diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 37f190d68..000000000 --- a/.babelrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "presets": ["babel-preset-vite", "@babel/preset-typescript"] -} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..5ace18ff8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.github +.husky +.svelte-kit +.vscode +build +e2e +node_modules +server/ui/assets \ No newline at end of file diff --git a/.env b/.env index 275ace610..3bb4a738a 100644 --- a/.env +++ b/.env @@ -1,2 +1,3 @@ -VITE_API="http://localhost:8080" +VITE_TEMPORAL_PORT="7233" +VITE_API="http://localhost:8233" VITE_MODE="development" diff --git a/.env.local-temporal b/.env.local-temporal new file mode 100644 index 000000000..537ae4af9 --- /dev/null +++ b/.env.local-temporal @@ -0,0 +1,4 @@ +VITE_TEMPORAL_PORT="7134" +VITE_API="http://localhost:8081" +VITE_MODE="development" +VITE_TEMPORAL_UI_BUILD_TARGET="local" diff --git a/.env.test b/.env.test new file mode 100644 index 000000000..b3e1eb90c --- /dev/null +++ b/.env.test @@ -0,0 +1,2 @@ +VITE_API="http://localhost:8233" +VITE_MODE="test" diff --git a/.env.test.e2e b/.env.test.e2e new file mode 100644 index 000000000..f7427d92c --- /dev/null +++ b/.env.test.e2e @@ -0,0 +1,4 @@ +VITE_TEMPORAL_UI_BUILD_TARGET="local" +VITE_TEMPORAL_PORT="7233" +VITE_API="http://localhost:8080" +VITE_MODE="development" \ No newline at end of file diff --git a/.env.test.integration b/.env.test.integration new file mode 100644 index 000000000..116d099f8 --- /dev/null +++ b/.env.test.integration @@ -0,0 +1,3 @@ +VITE_TEMPORAL_PORT="4444" +VITE_API="http://localhost:8233" +VITE_MODE="development" diff --git a/.env.testing b/.env.testing new file mode 100644 index 000000000..8e2e5390e --- /dev/null +++ b/.env.testing @@ -0,0 +1,4 @@ +VITE_API="http://localhost:7777" +VITE_TEMPORAL_PORT="6666" +VITE_UI_SERVER_PORT="7777" +VITE_MODE="testing" diff --git a/.env.ui-server b/.env.ui-server new file mode 100644 index 000000000..cd349b687 --- /dev/null +++ b/.env.ui-server @@ -0,0 +1,4 @@ +VITE_TEMPORAL_PORT="7233" +VITE_API="http://localhost:8081" +VITE_MODE="development" +VITE_TEMPORAL_UI_BUILD_TARGET="local" diff --git a/.eslintrc.cjs b/.eslintrc.cjs index ec7585e17..fea1921d4 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -5,19 +5,26 @@ module.exports = { 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier', + 'plugin:svelte/prettier', + 'plugin:storybook/recommended', ], - plugins: ['svelte3', '@typescript-eslint', 'vitest'], - ignorePatterns: ['*.cjs', 'prism.js'], + plugins: ['svelte', '@typescript-eslint', 'vitest', 'import'], + ignorePatterns: ['**/*.cjs', '/server', '**/error-boundary.svelte'], overrides: [ { - files: ['*.svelte'], - processor: 'svelte3/svelte3', + files: ['src/**/*.svelte'], + parser: 'svelte-eslint-parser', + parserOptions: { + parser: '@typescript-eslint/parser', + }, + }, + { + files: ['tests/**/*.spec.ts'], + extends: ['plugin:playwright/playwright-test'], }, ], - settings: { - 'svelte3/typescript': () => require('typescript'), - }, parserOptions: { + extraFileExtensions: ['.svelte'], sourceType: 'module', ecmaVersion: 2019, }, @@ -26,18 +33,76 @@ module.exports = { es2017: true, node: true, }, - overrides: [ - { - files: ['cypress/**/*.js'], - extends: ['plugin:cypress/recommended'], - }, - ], + globals: { + // These are needed until https://github.com/sveltejs/eslint-plugin-svelte/issues/348 is resolved + App: 'readonly', + $$Generic: 'readonly', + }, rules: { + quotes: ['error', 'single', { avoidEscape: true }], '@typescript-eslint/no-unused-vars': [ - 1, + 'error', { argsIgnorePattern: '^_', - varsIgnorePattern: '^_', + varsIgnorePattern: '^_|^\\$\\$(Props|Events|Slots)$', + }, + ], + '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/array-type': 'error', + '@typescript-eslint/class-literal-property-style': 'error', + '@typescript-eslint/consistent-generic-constructors': 'error', + '@typescript-eslint/consistent-type-assertions': [ + 'error', + { assertionStyle: 'as' }, + ], + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-restricted-imports': [ + 'error', + { + paths: [ + { + name: '$app/environment', + message: 'Please use esm-env instead.', + }, + ], + }, + ], + 'sort-imports': [ + 'error', + { ignoreCase: true, ignoreDeclarationSort: true }, + ], + 'import/order': [ + 'error', + { + groups: [ + 'builtin', + 'external', + 'internal', + ['parent', 'sibling', 'index'], + ], + pathGroups: [ + { + pattern: 'svelte/**', + group: 'external', + position: 'before', + }, + { pattern: '$app/**', group: 'external', position: 'after' }, + { pattern: './$types', group: 'external', position: 'after' }, + { pattern: '$lib/**', group: 'internal' }, + { + pattern: '$components/**/*.svelte', + group: 'internal', + position: 'after', + }, + { pattern: './**/*.svelte', group: 'index', position: 'after' }, + ], + pathGroupsExcludedImportTypes: ['svelte'], + 'newlines-between': 'always', + alphabetize: { + order: 'asc', + caseInsensitive: false, + orderImportKind: 'asc', + }, }, ], }, diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..6850022b3 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,18 @@ +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, +# @temporalio/frontend-engineering will be requested for +# review when someone opens a pull request. +* @temporalio/frontend-engineering +/src/routes/ @temporalio/frontend-engineering +/src/lib/holocene/ @rossedfort +/src/lib/components/ @rossedfort +/src/types @stevekinney +/src/lib/models @stevekinney @Alex-Tideman +/src/lib/services @stevekinney @Alex-Tideman +/src/lib/utilities @stevekinney @Alex-Tideman +/src/lib/stores @stevekinney @Alex-Tideman +/src/routes/(import) @Alex-Tideman +/src/lib/services/data-encoder.ts @Alex-Tideman @GiantRobots +/.husky/ @stevekinney +/.github/ @stevekinney +/.vscode/ @stevekinney diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md deleted file mode 100644 index 48e026c4c..000000000 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ /dev/null @@ -1,6 +0,0 @@ -## Description - - - -- [] Does this change require a design review? -- [] Does this change require manual testing? diff --git a/.github/WORKFLOWS.md b/.github/WORKFLOWS.md new file mode 100644 index 000000000..2d9376b33 --- /dev/null +++ b/.github/WORKFLOWS.md @@ -0,0 +1,427 @@ +# GitHub Workflows Documentation + +This document explains the automated workflows and custom actions used for version management and release processes in the Temporal UI repository. + +## 🎯 Overview + +The release management system enforces version bump PRs before any release can be published, maintains dual version sync between `package.json` and `version.go`, and leverages intelligent version increments with clear, automated workflows. + +**Version Source of Truth**: The Go `UIVersion` constant in `server/server/version/version.go` is the authoritative source of truth for the application version. All validation and comparison logic is based on this value, and `package.json` must be kept in sync with it. + +## 🧩 Custom Actions + +The workflow system uses 8 custom GitHub Actions for modular, reusable functionality: + +### Version Management Actions + +#### `.github/actions/validate-version-sync` + +- **Purpose**: Validates that package.json and version.go have matching versions +- **Inputs**: `force` (override validation failures) +- **Outputs**: `package-version`, `go-version`, `versions-match`, `current-version` +- **Source of Truth**: Uses Go UIVersion as authoritative version + +#### `.github/actions/calculate-version-bump` + +- **Purpose**: Calculates new semantic version based on bump type or specific version +- **Inputs**: `current-version`, `bump-type`, `specific-version`, `mode` +- **Outputs**: `new-version`, `version-changed`, `major`, `minor`, `patch` +- **Features**: Supports manual version override and semantic version validation + +#### `.github/actions/analyze-version-bump-type` + +- **Purpose**: Analyzes commits since last tag to determine version bump type +- **Inputs**: `last-tag`, `max-commits` +- **Outputs**: `bump-type`, `changelog`, `commit-count` +- **Logic**: Scans commit messages for breaking changes (major), features (minor), or fixes (patch) + +#### `.github/actions/update-version-files` + +- **Purpose**: Updates both package.json and version.go with new version atomically +- **Inputs**: `new-version`, `dry-run` +- **Outputs**: `files-updated`, `package-updated`, `go-updated` +- **Safety**: Validates successful updates and ensures files remain in sync + +### Release Validation Actions + +#### `.github/actions/validate-release-readiness` + +- **Purpose**: Validates that current version is ready for release (sync + version increase) +- **Outputs**: `version-ready`, `current-version`, `previous-version` +- **Validation**: Uses validation script to check sync and compares against last git tag + +#### `.github/actions/validate-published-release` + +- **Purpose**: Validates that published release version matches current codebase versions +- **Inputs**: `release-tag` +- **Outputs**: `release-version`, `validation-passed` +- **Checks**: Version format, file sync, and tag-to-code consistency + +### Build and Deployment Actions + +#### `.github/actions/build-and-package` + +- **Purpose**: Builds the UI package and creates tarball for release +- **Outputs**: `package-path`, `package-size` +- **Process**: Runs `pnpm package` and creates compressed tarball + +#### `.github/actions/update-version-from-release` + +- **Purpose**: Updates version.go to match published release tag +- **Inputs**: `release-tag` +- **Outputs**: `updated-version` +- **Use Case**: Post-release version synchronization + +## 📋 Workflow Architecture + +### 1. Version Bump Workflow (`.github/workflows/version-bump.yml`) + +**Purpose**: Creates PRs with updated versions based on merged changes or manual input. + +**Triggers**: + +- Manual dispatch via GitHub Actions UI + +**Inputs**: + +- `mode`: `auto` (analyze commits) | `manual` (specify type) | `dry-run` (preview only) +- `version_type`: `major` | `minor` | `patch` (used in manual mode) +- `specific_version`: Exact version to set (e.g., "2.38.0") - overrides version_type +- `force_update`: Override validation checks (admin only) + +**Custom Actions Used**: + +1. `validate-version-sync` - Ensures current versions match +2. `analyze-version-bump-type` - Analyzes commits for bump type (auto/dry-run modes) +3. `calculate-version-bump` - Calculates new version with override support +4. `update-version-files` - Updates both version files atomically + +**Process**: + +1. **Validation**: Ensures current versions are in sync using Go UIVersion as source of truth +2. **Analysis** (auto mode): Scans commit messages since last tag for version bump indicators +3. **Calculation**: Determines new version using semantic versioning or specific override +4. **Updates**: Modifies both `package.json` and `server/server/version/version.go` +5. **PR Creation**: Uses Peter Evans action to create pull request with changes and changelog + +**Example Usage**: + +```bash +# Auto mode - analyze recent commits +Actions → Version Bump → Run workflow → Mode: auto + +# Manual mode - specify version type +Actions → Version Bump → Run workflow → Mode: manual → Version Type: minor + +# Specific version - override with exact version +Actions → Version Bump → Run workflow → Mode: manual → Specific Version: 2.38.0 + +# Dry run - preview changes +Actions → Version Bump → Run workflow → Mode: dry-run +``` + +### 2. Release Draft Workflow (`.github/workflows/release-draft.yml`) + +**Purpose**: Automatically creates draft releases when version changes are detected. + +**Triggers**: + +- Push to `main` branch + +**Custom Actions Used**: + +1. `validate-release-readiness` - Validates version sync and increase + +**Process**: + +1. **Version Detection**: Uses custom action to compare current Go UIVersion with last git tag +2. **Validation**: Ensures package.json matches Go UIVersion (Go version is source of truth) +3. **Version Increase Check**: Verifies new version is higher than last released version +4. **Conditional Execution**: Only creates draft if validation passes +5. **Release Draft**: Uses Release Drafter with current version from UIVersion constant +6. **Downstream Trigger**: Notifies downstream repositories + +**Skip Conditions**: + +- No version increase detected (same or lower version than last tag) +- Version files are out of sync + +### 3. Release Published Workflow (`.github/workflows/release-published.yml`) + +**Purpose**: Validates release and builds packages when a draft release is published. + +**Triggers**: + +- Release published event + +**Custom Actions Used**: + +1. `validate-published-release` - Validates release tag matches code versions +2. `build-and-package` - Creates distributable package + +**Process**: + +1. **Version Validation**: Ensures release tag matches current version files using custom action +2. **Package Building**: Uses custom action to create distributable package +3. **Asset Upload**: Attaches package to release using custom action output +4. **Downstream Trigger**: Notifies downstream repositories + +## 🔄 Complete Release Process + +### For Repository Administrators + +#### Normal Development (No Changes Required) + +``` +Developer workflow remains unchanged: +✓ Create feature branches +✓ Submit PRs with appropriate labels (major/minor/patch) +✓ Merge PRs to main +✓ No version changes needed in regular PRs +``` + +#### Release Process + +##### Step 1: Version Bump + +```bash +1. Go to Actions → "Version Bump" +2. Click "Run workflow" +3. Choose mode: + • Auto: Analyze merged commits since last tag + • Manual: Specify major/minor/patch explicitly + • Specific: Set exact version (e.g., 2.38.0) + • Dry Run: Preview what version would be calculated +4. Review the workflow output +5. If successful, a PR will be created automatically using Peter Evans action +``` + +##### Step 2: Review and Merge Version Bump PR + +```bash +1. Review the auto-generated PR + • Check version changes in package.json and version.go + • Review changelog of changes included + • Verify version increment is correct +2. Merge the PR to main +``` + +##### Step 3: Draft Release Auto-Creation + +```bash +1. After version bump PR merges to main +2. Release Draft workflow automatically triggers +3. Custom action validates version readiness +4. Creates draft release with version from UIVersion constant +5. Draft includes auto-generated release notes +``` + +##### Step 4: Publish Release + +```bash +1. Go to Releases → Draft release +2. Review auto-generated content +3. Edit release notes if needed +4. Click "Publish release" +5. Release Published workflow validates and handles packaging +``` + +## 🛠️ Local Development Tools + +### Version Validation Script + +**Location**: `scripts/validate-versions.sh` + +**Usage**: + +```bash +# Validate versions match and increased +pnpm validate:versions +# or directly: +./scripts/validate-versions.sh + +# Quiet output (CI-friendly) +./scripts/validate-versions.sh --quiet + +# Help +./scripts/validate-versions.sh --help +``` + +**Purpose**: + +- Ensures `package.json` matches `version.go` UIVersion (Go version is source of truth) +- Verifies current version has increased compared to last git tag (not last commit) +- Acts as gate-keeper for draft release creation +- Used by `validate-release-readiness` custom action + +## 🚨 Error Scenarios & Recovery + +### No Draft Release Created + +**Symptoms**: Version bump PR merged but no draft release appears + +**Diagnosis**: + +1. Check if both version files were updated in the PR +2. Verify versions in both files match exactly +3. Check Release Draft workflow logs and custom action outputs + +**Resolution**: + +```bash +1. If versions don't match: + → Run Version Bump workflow again + → Or manually sync versions and push to main + +2. If workflow failed: + → Check workflow logs for custom action failures + → Re-run failed jobs if applicable +``` + +### Version Mismatch Detected + +**Symptoms**: Workflows fail with version sync errors from custom actions + +**Diagnosis**: `package.json` and `version.go` have different versions + +**Resolution**: + +```bash +1. Immediate fix: + → Run: pnpm validate:versions + → Check which file is incorrect (Go UIVersion is source of truth) + → Manually update mismatched file + → Commit and push changes + +2. Proper fix: + → Use Version Bump workflow with force_update: true + → This will sync both files to correct version using custom actions +``` + +### Release Publication Fails + +**Symptoms**: Published release fails validation from custom action + +**Diagnosis**: Release tag doesn't match current version files + +**Resolution**: + +```bash +1. Check release tag format (should be vX.Y.Z or X.Y.Z) +2. Verify tag matches package.json and version.go versions +3. If mismatch: + → Delete the release + → Fix version sync issues using custom actions + → Re-run Version Bump workflow + → Create new release +``` + +## 🔧 Maintenance & Troubleshooting + +### Common Issues + +#### Custom Action Failures + +- Check individual custom action logs in workflow runs +- Verify inputs are correctly passed between actions +- Ensure repository permissions allow custom action execution + +#### Workflow Permissions + +- Ensure `GITHUB_TOKEN` has write permissions for contents and PRs +- Verify `TEMPORAL_CICD_APP_ID` and `TEMPORAL_CICD_PRIVATE_KEY` secrets exist +- Custom actions require same permissions as parent workflows + +#### Version Format Issues + +- All versions must follow semantic versioning (X.Y.Z) +- No prefixes like 'v' in version files (only in git tags) +- Custom actions validate semantic version format + +#### Branch Protection + +- Ensure main branch allows admin overrides for automated PRs +- Version bump PRs should be exempt from certain checks +- Peter Evans action requires appropriate permissions + +### Debugging Workflows + +1. **Check Workflow Logs**: Actions tab → Select workflow run → View detailed logs including custom action steps +2. **Validate Locally**: Run `pnpm validate:versions` before debugging +3. **Test Version Bump**: Use dry-run mode to preview changes without custom action modifications +4. **Manual Intervention**: Force update option available for emergencies + +### Emergency Procedures + +#### Bypass Version Workflow + +```bash +# Only in emergencies - manually sync versions +1. Update package.json version +2. Update version.go UIVersion (must match package.json) +3. Commit both changes +4. Create release manually +``` + +#### Rollback Version + +```bash +# If wrong version was released +1. Revert version bump PR +2. Delete incorrect release and tag +3. Re-run Version Bump workflow (custom actions will recalculate) +4. Create new release +``` + +## 📊 Technical Implementation Details + +### Custom Action Architecture + +- **Modular Design**: Each action has single responsibility +- **Reusable Components**: Actions used across multiple workflows +- **Error Handling**: Individual actions provide detailed error messages +- **Output Chaining**: Actions pass data between workflow steps + +### Version Comparison Logic + +- **Git Tag Based**: Compares against last released version, not last commit +- **Semantic Versioning**: Uses proper semver comparison algorithms +- **Go Source of Truth**: Always uses UIVersion constant as authoritative source + +### Peter Evans Integration + +- **Reliable PR Creation**: Uses battle-tested create-pull-request action +- **Rich PR Content**: Includes changelog and detailed commit information +- **Branch Management**: Automatic branch creation and cleanup + +## 📚 Additional Resources + +- [Semantic Versioning](https://semver.org/) +- [GitHub Actions Documentation](https://docs.github.com/en/actions) +- [Custom Actions Documentation](https://docs.github.com/en/actions/creating-actions) +- [Peter Evans Actions](https://github.com/peter-evans) +- [Release Drafter Configuration](https://github.com/release-drafter/release-drafter) + +## 🔄 File Locations + +### Workflows + +- Version Bump: `.github/workflows/version-bump.yml` +- Release Draft: `.github/workflows/release-draft.yml` +- Release Published: `.github/workflows/release-published.yml` + +### Custom Actions + +- Validate Version Sync: `.github/actions/validate-version-sync/action.yml` +- Calculate Version Bump: `.github/actions/calculate-version-bump/action.yml` +- Analyze Version Bump Type: `.github/actions/analyze-version-bump-type/action.yml` +- Update Version Files: `.github/actions/update-version-files/action.yml` +- Validate Release Readiness: `.github/actions/validate-release-readiness/action.yml` +- Validate Published Release: `.github/actions/validate-published-release/action.yml` +- Build and Package: `.github/actions/build-and-package/action.yml` +- Update Version from Release: `.github/actions/update-version-from-release/action.yml` + +### Configuration + +- Release Drafter Config: `.github/release-drafter.yml` +- Version Validation Script: `scripts/validate-versions.sh` diff --git a/.github/actions/analyze-version-bump-type/action.yml b/.github/actions/analyze-version-bump-type/action.yml new file mode 100644 index 000000000..a0a5d57f6 --- /dev/null +++ b/.github/actions/analyze-version-bump-type/action.yml @@ -0,0 +1,98 @@ +name: 'Analyze Version Bump Type' +description: 'Analyzes commits since last tag to determine version bump type' +inputs: + last-tag: + description: 'Last version tag to analyze from' + required: true + max-commits: + description: 'Maximum commits to analyze if no tag found' + required: false + default: '10' +outputs: + bump-type: + description: 'Determined bump type (major, minor, patch)' + value: ${{ steps.analyze.outputs.bump-type }} + changelog: + description: 'Formatted changelog of commits' + value: ${{ steps.analyze.outputs.changelog }} + commit-count: + description: 'Number of commits analyzed' + value: ${{ steps.analyze.outputs.commit-count }} + +runs: + using: 'composite' + steps: + - name: Analyze commits for bump type + id: analyze + shell: bash + run: | + LAST_TAG="${{ inputs.last-tag }}" + MAX_COMMITS="${{ inputs.max-commits }}" + + echo "🔍 Analyzing commits since: $LAST_TAG" + + # Get commits since last tag + if [[ "$LAST_TAG" == "v0.0.0" ]] || [[ -z "$LAST_TAG" ]]; then + echo "📝 No previous tag found, analyzing last $MAX_COMMITS commits" + COMMITS=$(git log --oneline --format="%H %s" HEAD~${MAX_COMMITS}..HEAD 2>/dev/null || git log --oneline --format="%H %s" --max-count=$MAX_COMMITS) + else + echo "📝 Analyzing commits since $LAST_TAG" + COMMITS=$(git log --oneline --format="%H %s" ${LAST_TAG}..HEAD 2>/dev/null || echo "") + fi + + if [[ -z "$COMMITS" ]]; then + echo "⚠️ No commits found to analyze" + echo "bump-type=patch" >> $GITHUB_OUTPUT + echo "changelog=No recent commits found" >> $GITHUB_OUTPUT + echo "commit-count=0" >> $GITHUB_OUTPUT + exit 0 + fi + + # Count commits + COMMIT_COUNT=$(echo "$COMMITS" | wc -l | tr -d ' ') + echo "📊 Found $COMMIT_COUNT commits to analyze" + + # Analyze commit messages for version bump indicators + BUMP_TYPE="patch" # Default to patch + + echo "🔍 Scanning commit messages..." + + # Check for breaking changes (major) + if echo "$COMMITS" | grep -iE "(BREAKING|breaking change|major)" >/dev/null; then + BUMP_TYPE="major" + echo "🚨 Found breaking changes → major bump" + # Check for features (minor) + elif echo "$COMMITS" | grep -iE "(feat|feature|minor|add)" >/dev/null; then + BUMP_TYPE="minor" + echo "✨ Found new features → minor bump" + # Default to patch for fixes, chores, etc. + else + echo "🔧 Found fixes/improvements → patch bump" + fi + + # Generate changelog + CHANGELOG=$(echo "$COMMITS" | sed 's/^[a-f0-9]* /- /' | head -20) + + echo "📋 Changelog preview:" + echo "$CHANGELOG" | head -5 + if [[ $COMMIT_COUNT -gt 5 ]]; then + echo "... and $((COMMIT_COUNT - 5)) more commits" + fi + + # Set outputs + echo "bump-type=$BUMP_TYPE" >> $GITHUB_OUTPUT + echo "commit-count=$COMMIT_COUNT" >> $GITHUB_OUTPUT + + # Use heredoc for multiline changelog + cat >> $GITHUB_OUTPUT << 'EOF' + changelog<> $GITHUB_ENV diff --git a/.github/actions/build-and-package/action.yml b/.github/actions/build-and-package/action.yml new file mode 100644 index 000000000..b1712a4b0 --- /dev/null +++ b/.github/actions/build-and-package/action.yml @@ -0,0 +1,36 @@ +name: 'Build and Package' +description: 'Builds the UI package and creates tarball for release' +outputs: + package-path: + description: 'Path to the generated package tarball' + value: ${{ steps.package.outputs.package-path }} + package-size: + description: 'Size of the generated package' + value: ${{ steps.package.outputs.package-size }} + +runs: + using: 'composite' + steps: + - name: Build and package + id: package + shell: bash + run: | + echo "🏗️ Building UI package..." + + # Build the package + pnpm package + + # Create tarball from dist folder + PACKAGE_PATH="./temporal-ui-package.tar.gz" + tar -czf "$PACKAGE_PATH" dist + + # Get package size for logging + PACKAGE_SIZE=$(ls -lh "$PACKAGE_PATH" | awk '{print $5}') + + echo "✅ Package built successfully" + echo "📦 Package path: $PACKAGE_PATH" + echo "📊 Package size: $PACKAGE_SIZE" + + # Set outputs + echo "package-path=$PACKAGE_PATH" >> $GITHUB_OUTPUT + echo "package-size=$PACKAGE_SIZE" >> $GITHUB_OUTPUT diff --git a/.github/actions/calculate-version-bump/action.yml b/.github/actions/calculate-version-bump/action.yml new file mode 100644 index 000000000..66d225e44 --- /dev/null +++ b/.github/actions/calculate-version-bump/action.yml @@ -0,0 +1,130 @@ +name: 'Calculate Version Bump' +description: 'Calculates new semantic version based on bump type' +inputs: + current-version: + description: 'Current semantic version' + required: true + bump-type: + description: 'Type of version bump (major, minor, patch)' + required: false + specific-version: + description: 'Specific version to set (overrides bump-type)' + required: false + mode: + description: 'Bump mode for context (auto, manual, dry-run)' + required: false + default: 'auto' +outputs: + new-version: + description: 'Calculated new version' + value: ${{ steps.calculate.outputs.new-version }} + version-changed: + description: 'Whether version will change' + value: ${{ steps.calculate.outputs.version-changed }} + major: + description: 'Major version number' + value: ${{ steps.parse.outputs.major }} + minor: + description: 'Minor version number' + value: ${{ steps.parse.outputs.minor }} + patch: + description: 'Patch version number' + value: ${{ steps.parse.outputs.patch }} + +runs: + using: 'composite' + steps: + - name: Parse current version + id: parse + shell: bash + run: | + CURRENT_VERSION="${{ inputs.current-version }}" + + echo "🔍 Parsing version: $CURRENT_VERSION" + + # Validate semantic version format + if [[ ! $CURRENT_VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then + echo "❌ Invalid semantic version format: $CURRENT_VERSION" + echo "Expected format: x.y.z (e.g., 1.2.3)" + exit 1 + fi + + MAJOR=${BASH_REMATCH[1]} + MINOR=${BASH_REMATCH[2]} + PATCH=${BASH_REMATCH[3]} + + echo "major=$MAJOR" >> $GITHUB_OUTPUT + echo "minor=$MINOR" >> $GITHUB_OUTPUT + echo "patch=$PATCH" >> $GITHUB_OUTPUT + + echo "📊 Current: $MAJOR.$MINOR.$PATCH" + + - name: Calculate new version + id: calculate + shell: bash + run: | + CURRENT_VERSION="${{ inputs.current-version }}" + SPECIFIC_VERSION="${{ inputs.specific-version }}" + BUMP_TYPE="${{ inputs.bump-type }}" + MAJOR="${{ steps.parse.outputs.major }}" + MINOR="${{ steps.parse.outputs.minor }}" + PATCH="${{ steps.parse.outputs.patch }}" + + # Check if specific version was provided + if [[ -n "$SPECIFIC_VERSION" ]]; then + echo "🎯 Using specific version: $SPECIFIC_VERSION" + + # Validate specific version format + if [[ ! $SPECIFIC_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "❌ Invalid specific version format: $SPECIFIC_VERSION" + echo "Expected format: x.y.z (e.g., 2.38.0)" + exit 1 + fi + + NEW_VERSION="$SPECIFIC_VERSION" + + # Compare with current version + if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then + echo "⚠️ Specific version same as current: $NEW_VERSION" + elif [[ "$(printf '%s\n' "$CURRENT_VERSION" "$NEW_VERSION" | sort -V | head -n1)" == "$NEW_VERSION" ]]; then + echo "⚠️ Specific version ($NEW_VERSION) is older than current ($CURRENT_VERSION)" + echo "💡 Consider using a higher version number" + else + echo "✅ Specific version is newer than current" + fi + else + echo "🎯 Bump type: $BUMP_TYPE" + + # Calculate new version based on bump type + case $BUMP_TYPE in + major) + NEW_VERSION="$((MAJOR + 1)).0.0" + echo "📈 Major bump: $CURRENT_VERSION → $NEW_VERSION" + ;; + minor) + NEW_VERSION="$MAJOR.$((MINOR + 1)).0" + echo "📈 Minor bump: $CURRENT_VERSION → $NEW_VERSION" + ;; + patch) + NEW_VERSION="$MAJOR.$MINOR.$((PATCH + 1))" + echo "📈 Patch bump: $CURRENT_VERSION → $NEW_VERSION" + ;; + *) + echo "❌ Invalid bump type: $BUMP_TYPE" + echo "Valid types: major, minor, patch" + exit 1 + ;; + esac + fi + + # Check if version actually changed + if [[ "$CURRENT_VERSION" != "$NEW_VERSION" ]]; then + VERSION_CHANGED="true" + echo "✅ Version will change: $CURRENT_VERSION → $NEW_VERSION" + else + VERSION_CHANGED="false" + echo "ℹ️ No version change (already at $CURRENT_VERSION)" + fi + + echo "new-version=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "version-changed=$VERSION_CHANGED" >> $GITHUB_OUTPUT diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml new file mode 100644 index 000000000..f9313ab6f --- /dev/null +++ b/.github/actions/setup-node/action.yml @@ -0,0 +1,37 @@ +name: 'Install Node and Dependencies' +description: 'Run pnpm install' + +runs: + using: 'composite' + steps: + - name: Setup pnpm + uses: pnpm/action-setup@v4.0.0 + with: + run_install: false + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + registry-url: 'https://registry.npmjs.org' + cache: 'pnpm' + + - name: Get pnpm store directory + id: pnpm-cache + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v3 + name: Setup pnpm cache + with: + path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + shell: bash + run: pnpm install + env: + HUSKY: '0' diff --git a/.github/actions/update-version-files/action.yml b/.github/actions/update-version-files/action.yml new file mode 100644 index 000000000..1a23a75f3 --- /dev/null +++ b/.github/actions/update-version-files/action.yml @@ -0,0 +1,93 @@ +name: 'Update Version Files' +description: 'Updates both package.json and version.go with new version' +inputs: + new-version: + description: 'New version to set' + required: true + dry-run: + description: 'Only show what would be changed without modifying files' + required: false + default: 'false' +outputs: + files-updated: + description: 'Whether files were actually updated' + value: ${{ steps.update.outputs.files-updated }} + package-updated: + description: 'Whether package.json was updated' + value: ${{ steps.update.outputs.package-updated }} + go-updated: + description: 'Whether version.go was updated' + value: ${{ steps.update.outputs.go-updated }} + +runs: + using: 'composite' + steps: + - name: Update version files + id: update + shell: bash + run: | + NEW_VERSION="${{ inputs.new-version }}" + DRY_RUN="${{ inputs.dry-run }}" + + echo "🎯 Target version: $NEW_VERSION" + + # Validate semantic version format + if [[ ! $NEW_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "❌ Invalid semantic version format: $NEW_VERSION" + exit 1 + fi + + if [[ "$DRY_RUN" == "true" ]]; then + echo "🔍 DRY RUN - Would update:" + echo " 📦 package.json: $(jq -r '.version' package.json) → $NEW_VERSION" + echo " 🔧 version.go: $(grep 'UIVersion.*=' server/server/version/version.go | sed 's/.*"\(.*\)".*/\1/') → $NEW_VERSION" + echo "files-updated=false" >> $GITHUB_OUTPUT + echo "package-updated=false" >> $GITHUB_OUTPUT + echo "go-updated=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # Update package.json + echo "📦 Updating package.json..." + ORIGINAL_PACKAGE_VERSION=$(jq -r '.version' package.json) + jq ".version = \"$NEW_VERSION\"" package.json > tmp.json && mv tmp.json package.json + UPDATED_PACKAGE_VERSION=$(jq -r '.version' package.json) + + if [[ "$UPDATED_PACKAGE_VERSION" == "$NEW_VERSION" ]]; then + echo "✅ package.json updated: $ORIGINAL_PACKAGE_VERSION → $NEW_VERSION" + echo "package-updated=true" >> $GITHUB_OUTPUT + else + echo "❌ Failed to update package.json" + echo "package-updated=false" >> $GITHUB_OUTPUT + exit 1 + fi + + # Update version.go + echo "🔧 Updating version.go..." + ORIGINAL_GO_VERSION=$(grep 'UIVersion.*=' server/server/version/version.go | sed 's/.*"\(.*\)".*/\1/') + sed -i "s/UIVersion.*=.*\".*\"/UIVersion = \"$NEW_VERSION\"/" server/server/version/version.go + UPDATED_GO_VERSION=$(grep 'UIVersion.*=' server/server/version/version.go | sed 's/.*"\(.*\)".*/\1/') + + if [[ "$UPDATED_GO_VERSION" == "$NEW_VERSION" ]]; then + echo "✅ version.go updated: $ORIGINAL_GO_VERSION → $NEW_VERSION" + echo "go-updated=true" >> $GITHUB_OUTPUT + else + echo "❌ Failed to update version.go" + echo "go-updated=false" >> $GITHUB_OUTPUT + exit 1 + fi + + # Verify both files now match + FINAL_PACKAGE_VERSION=$(jq -r '.version' package.json) + FINAL_GO_VERSION=$(grep 'UIVersion.*=' server/server/version/version.go | sed 's/.*"\(.*\)".*/\1/') + + if [[ "$FINAL_PACKAGE_VERSION" == "$FINAL_GO_VERSION" ]] && [[ "$FINAL_PACKAGE_VERSION" == "$NEW_VERSION" ]]; then + echo "✅ Both files successfully updated and in sync: $NEW_VERSION" + echo "files-updated=true" >> $GITHUB_OUTPUT + else + echo "❌ Files are not in sync after update" + echo " package.json: $FINAL_PACKAGE_VERSION" + echo " version.go: $FINAL_GO_VERSION" + echo "files-updated=false" >> $GITHUB_OUTPUT + exit 1 + fi diff --git a/.github/actions/update-version-from-release/action.yml b/.github/actions/update-version-from-release/action.yml new file mode 100644 index 000000000..366df9e54 --- /dev/null +++ b/.github/actions/update-version-from-release/action.yml @@ -0,0 +1,40 @@ +name: 'Update Version from Release' +description: 'Updates version.go to match the published release tag' +inputs: + release-tag: + description: 'Release tag from GitHub release event' + required: true +outputs: + updated-version: + description: 'Version that was set in the files' + value: ${{ steps.update.outputs.updated-version }} + +runs: + using: 'composite' + steps: + - name: Update version in source code + id: update + shell: bash + run: | + # Extract version from release tag (remove 'v' prefix if present) + RELEASE_TAG="${{ inputs.release-tag }}" + VERSION="${RELEASE_TAG#v}" + + echo "🔄 Updating version file to: $VERSION" + + # Update the version.go file + sed -i "s/UIVersion = \".*\"/UIVersion = \"$VERSION\"/" server/server/version/version.go + + # Verify the update + UPDATED_VERSION=$(grep 'UIVersion.*=' server/server/version/version.go | sed 's/.*"\(.*\)".*/\1/') + + if [[ "$UPDATED_VERSION" == "$VERSION" ]]; then + echo "✅ Version.go updated successfully: $VERSION" + else + echo "❌ Failed to update version.go" + echo " Expected: $VERSION" + echo " Actual: $UPDATED_VERSION" + exit 1 + fi + + echo "updated-version=$VERSION" >> $GITHUB_OUTPUT diff --git a/.github/actions/validate-published-release/action.yml b/.github/actions/validate-published-release/action.yml new file mode 100644 index 000000000..dc8a5c696 --- /dev/null +++ b/.github/actions/validate-published-release/action.yml @@ -0,0 +1,69 @@ +name: 'Validate Published Release' +description: 'Validates that published release version matches current codebase versions' +inputs: + release-tag: + description: 'Release tag from GitHub release event' + required: true +outputs: + release-version: + description: 'Release version (without v prefix)' + value: ${{ steps.validate.outputs.release-version }} + validation-passed: + description: 'Whether validation passed' + value: ${{ steps.validate.outputs.validation-passed }} + +runs: + using: 'composite' + steps: + - name: Validate release version + id: validate + shell: bash + run: | + RELEASE_TAG="${{ inputs.release-tag }}" + + echo "🔍 Validating release version..." + + # Remove 'v' prefix if present + RELEASE_VERSION=${RELEASE_TAG#v} + + # Validate semantic version format + if [[ ! $RELEASE_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "❌ Invalid release version format: $RELEASE_VERSION" + echo "Expected format: x.y.z (e.g., 2.38.0)" + echo "validation-passed=false" >> $GITHUB_OUTPUT + exit 1 + fi + + # Get current versions from files + PACKAGE_VERSION=$(jq -r '.version' package.json) + GO_VERSION=$(grep 'UIVersion.*=' server/server/version/version.go | sed 's/.*"\(.*\)".*/\1/') + + echo "📊 Release tag: $RELEASE_TAG" + echo "📊 Release version: $RELEASE_VERSION" + echo "📊 Package.json version: $PACKAGE_VERSION" + echo "📊 Version.go version: $GO_VERSION" + + # Validate versions are in sync + if [[ "$PACKAGE_VERSION" != "$GO_VERSION" ]]; then + echo "❌ Version files are not in sync!" + echo " package.json: $PACKAGE_VERSION" + echo " version.go: $GO_VERSION" + echo "validation-passed=false" >> $GITHUB_OUTPUT + exit 1 + fi + + # Validate release version matches current version + if [[ "$RELEASE_VERSION" != "$PACKAGE_VERSION" ]]; then + echo "❌ Release version does not match current version!" + echo " Release: $RELEASE_VERSION" + echo " Current: $PACKAGE_VERSION" + echo "" + echo "💡 Please ensure the release tag matches the version in the code." + echo "💡 Use the Version Bump workflow to update versions before creating a release." + echo "validation-passed=false" >> $GITHUB_OUTPUT + exit 1 + fi + + echo "✅ Version validation passed: $RELEASE_VERSION" + echo "release-version=$RELEASE_VERSION" >> $GITHUB_OUTPUT + echo "validation-passed=true" >> $GITHUB_OUTPUT diff --git a/.github/actions/validate-release-readiness/action.yml b/.github/actions/validate-release-readiness/action.yml new file mode 100644 index 000000000..5f26f6f35 --- /dev/null +++ b/.github/actions/validate-release-readiness/action.yml @@ -0,0 +1,50 @@ +name: 'Validate Release Readiness' +description: 'Validates that current version is ready for release (sync + version increase)' +outputs: + version-ready: + description: 'Whether version is ready for release' + value: ${{ steps.validate.outputs.version-ready }} + current-version: + description: 'Current version from version.go' + value: ${{ steps.validate.outputs.current-version }} + previous-version: + description: 'Previous version from last commit' + value: ${{ steps.validate.outputs.previous-version }} + +runs: + using: 'composite' + steps: + - name: Validate version for release readiness + id: validate + shell: bash + run: | + echo "🔍 Validating release readiness..." + + # Use the validation script to check both sync and increase + if ./scripts/validate-versions.sh --quiet; then + echo "✅ Version validation passed" + VERSION_READY="true" + else + echo "❌ Version validation failed" + VERSION_READY="false" + fi + + # Get version details for outputs (using Go version as source of truth) + CURRENT_VERSION=$(grep 'UIVersion.*=' server/server/version/version.go | sed 's/.*"\(.*\)".*/\1/') + + # Get version from last tag instead of last commit + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + if [[ -n "$LAST_TAG" ]]; then + # Remove 'v' prefix if present + PREVIOUS_VERSION="${LAST_TAG#v}" + else + PREVIOUS_VERSION="" + fi + + echo "📊 Current version: $CURRENT_VERSION" + echo "📊 Previous version: $PREVIOUS_VERSION" + + # Set outputs + echo "version-ready=$VERSION_READY" >> $GITHUB_OUTPUT + echo "current-version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + echo "previous-version=$PREVIOUS_VERSION" >> $GITHUB_OUTPUT diff --git a/.github/actions/validate-version-sync/action.yml b/.github/actions/validate-version-sync/action.yml new file mode 100644 index 000000000..78fe61acf --- /dev/null +++ b/.github/actions/validate-version-sync/action.yml @@ -0,0 +1,62 @@ +name: 'Validate Version Sync' +description: 'Validates that package.json and version.go have matching versions' +inputs: + force: + description: 'Force validation to pass even with mismatches' + required: false + default: 'false' +outputs: + package-version: + description: 'Version from package.json' + value: ${{ steps.extract.outputs.package-version }} + go-version: + description: 'Version from version.go (source of truth)' + value: ${{ steps.extract.outputs.go-version }} + versions-match: + description: 'Whether versions are in sync' + value: ${{ steps.validate.outputs.versions-match }} + current-version: + description: 'Current version (Go version as source of truth)' + value: ${{ steps.extract.outputs.go-version }} + +runs: + using: 'composite' + steps: + - name: Extract versions + id: extract + shell: bash + run: | + PACKAGE_VERSION=$(jq -r '.version' package.json) + GO_VERSION=$(grep 'UIVersion.*=' server/server/version/version.go | sed 's/.*"\(.*\)".*/\1/') + + echo "package-version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT + echo "go-version=$GO_VERSION" >> $GITHUB_OUTPUT + + echo "📦 package.json version: $PACKAGE_VERSION" + echo "🔧 version.go version: $GO_VERSION (source of truth)" + + - name: Validate sync + id: validate + shell: bash + run: | + PACKAGE_VERSION="${{ steps.extract.outputs.package-version }}" + GO_VERSION="${{ steps.extract.outputs.go-version }}" + FORCE="${{ inputs.force }}" + + if [[ "$PACKAGE_VERSION" == "$GO_VERSION" ]]; then + echo "versions-match=true" >> $GITHUB_OUTPUT + echo "✅ Versions are in sync: $GO_VERSION" + else + echo "versions-match=false" >> $GITHUB_OUTPUT + echo "❌ Version mismatch detected!" + echo " package.json: $PACKAGE_VERSION" + echo " version.go: $GO_VERSION (source of truth)" + + if [[ "$FORCE" != "true" ]]; then + echo "💡 Fix this by updating package.json to match version.go" + echo "💡 Or use the Version Bump workflow to sync both files" + exit 1 + else + echo "⚠️ Continuing due to force=true" + fi + fi diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..1a115da31 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,34 @@ + + + + + +## Description & motivation 💭 + +### Screenshots (if applicable) 📸 + +### Design Considerations 🎨 + +## Testing 🧪 + +### How was this tested 👻 + +- [ ] Manual testing +- [ ] E2E tests added +- [ ] Unit tests added + +### Steps for others to test: 🚶🏽‍♂️🚶🏽‍♀️ + +## Checklists + +### Draft Checklist + +### Merge Checklist + +### Issue(s) closed + +## Docs + +### Any docs updates needed? diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 000000000..06591c32d --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,20 @@ +# https://github.com/release-drafter/release-drafter?tab=readme-ov-file#configuration-options +name-template: 'v$RESOLVED_VERSION 🤖' +tag-template: 'v$RESOLVED_VERSION' +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. +version-resolver: + major: + labels: + - 'major' + minor: + labels: + - 'minor' + patch: + labels: + - 'patch' + default: patch +template: | + ## What’s Changed + + $CHANGES diff --git a/.github/workflows/check-types.yml b/.github/workflows/check-types.yml deleted file mode 100644 index dbc772b41..000000000 --- a/.github/workflows/check-types.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Check Types - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: pnpm/action-setup@v2.2.2 - with: - version: 7 - - name: Install modules - run: pnpm install - - name: Check Types - run: pnpm run check diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml new file mode 100644 index 000000000..6ea7ec866 --- /dev/null +++ b/.github/workflows/chromatic.yml @@ -0,0 +1,28 @@ +name: 'Chromatic' + +on: + push: + branches: [main] + pull_request_target: + branches: [main, 'codefreeze-*'] + +jobs: + chromatic: + name: Run Chromatic + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Checkout and Setup Node + uses: ./.github/actions/setup-node + - name: Run Chromatic + uses: chromaui/action@latest + with: + projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} + autoAcceptChanges: 'main' + exitZeroOnChanges: true + exitOnceUploaded: true + onlyChanged: true + skip: 'dependabot/**' diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml deleted file mode 100644 index 180f69bc5..000000000 --- a/.github/workflows/integration-tests.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Cypress Tests - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - cypress-run: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Setup - uses: pnpm/action-setup@v2.2.2 - with: - version: 7 - - name: Install dependencies - run: pnpm install - - name: Build assets - run: VITE_MODE=test pnpm run build:local - - name: Run Cypress tests - uses: cypress-io/github-action@v2 - with: - install: false - start: pnpm run preview:local - wait-on: 'http://localhost:3000' - wait-on-timeout: 120 - browser: chrome - record: true - env: - CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} - VITE_MODE: test diff --git a/.github/workflows/lint-and-test.yml b/.github/workflows/lint-and-test.yml new file mode 100644 index 000000000..b050f5df9 --- /dev/null +++ b/.github/workflows/lint-and-test.yml @@ -0,0 +1,46 @@ +name: Run Tests, Lint, and Check Types +run-name: ${{github.event.pull_request.title}} (${{ github.event.pull_request.number }}) by @${{ github.triggering_actor }} (Attempt ${{ github.run_attempt }}) + +on: + push: + branches: [main] + pull_request: + branches: [main, 'codefreeze-*'] + paths-ignore: + - '**.md' + - 'LICENSE' + - 'CODEOWNERS' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Checkout and Setup Node + uses: ./.github/actions/setup-node + - name: Lint + run: pnpm run --if-present lint:ci + check-types: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Checkout and Setup Node + uses: ./.github/actions/setup-node + - name: Check Types + run: pnpm run check + unit-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Checkout and Setup Node + uses: ./.github/actions/setup-node + - name: Run Unit Tests + run: pnpm test diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 882307321..000000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Lint - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: pnpm/action-setup@v2.2.2 - with: - version: 7 - - name: Install modules - run: pnpm install - - name: Lint - run: pnpm run --if-present lint diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 000000000..30b264313 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,74 @@ +name: Playwright +run-name: ${{github.event.pull_request.title}} (${{ github.event.pull_request.number }}) by @${{ github.triggering_actor }} (Attempt ${{ github.run_attempt }}) + +on: + push: + branches: [main] + pull_request: + branches: [main, 'codefreeze-*'] + paths-ignore: + - '**.md' + - 'LICENSE' + - 'CODEOWNERS' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + integration-tests: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Checkout and Setup Node + uses: ./.github/actions/setup-node + - name: Install Playwright Browsers + run: pnpm exec playwright install --with-deps + - name: Run Integration tests + run: pnpm test:integration + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report-integration + path: | + ./playwright-report/ + ./test-results/ + retention-days: 30 + e2e-tests: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Checkout and Setup Node + uses: ./.github/actions/setup-node + - name: Build UI + run: pnpm build:server + - uses: actions/setup-go@v3 + with: + go-version-file: server/go.mod + cache-dependency-path: server/go.sum + cache: true + check-latest: true + - name: Set up Protoc + uses: arduino/setup-protoc@v3 + with: + version: '30.x' + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Build UI Server + working-directory: server + run: make build + - name: Install Playwright Browsers + run: pnpm exec playwright install --with-deps + - name: Run E2E tests + run: pnpm test:e2e + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report-e2e + path: | + ./playwright-report/ + ./test-results/ + retention-days: 30 diff --git a/.github/workflows/publish-package.yml b/.github/workflows/publish-package.yml deleted file mode 100644 index fa40dd8c8..000000000 --- a/.github/workflows/publish-package.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: Publish package to npm - -on: - workflow_dispatch: - branches: - - main - inputs: - bump: - description: 'bump type, major or minor or patch' - default: 'patch' - -jobs: - release: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Node setup - uses: actions/setup-node@v3 - with: - node-version: 16 - registry-url: 'https://registry.npmjs.org' - - name: pnpm setup - uses: pnpm/action-setup@v2.2.2 - with: - version: 7 - - name: Configure Git - run: | - git config --local user.name 'Temporal Data (cicd)' - git config --local user.email 'commander-data@temporal.io' - - name: Install - run: pnpm install - - name: Build - run: pnpm run build:local - - name: Bump version - run: pnpm run package:${{ inputs.bump }} - - name: Publish package to NPM - run: npm publish --access public - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - working-directory: ./package - - name: Commit and push version bump - run: | - git remote set-url origin https://x-access-token:${{ secrets.COMMANDER_DATA_TOKEN }}@github.com/${{ github.repository }} - git push origin ${{ github.ref_name }} diff --git a/.github/workflows/release-draft.yml b/.github/workflows/release-draft.yml new file mode 100644 index 000000000..4b9b9d9f4 --- /dev/null +++ b/.github/workflows/release-draft.yml @@ -0,0 +1,91 @@ +name: Release Drafter + +on: + push: + branches: + - main + +permissions: + contents: read + +jobs: + check_version_change: + runs-on: ubuntu-latest + outputs: + version-ready: ${{ steps.version-check.outputs.version-ready }} + current-version: ${{ steps.version-check.outputs.current-version }} + previous-version: ${{ steps.version-check.outputs.previous-version }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Validate version for release readiness + id: version-check + uses: ./.github/actions/validate-release-readiness + + update_release_draft: + needs: check_version_change + # Only run if version validation passed (sync + increase) + if: needs.check_version_change.outputs.version-ready == 'true' + + permissions: + contents: write + pull-requests: write + + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Create release draft + uses: release-drafter/release-drafter@v6 + with: + version: ${{ needs.check_version_change.outputs.current-version }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Prepare dispatch token + id: dispatch_token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.TEMPORAL_CICD_APP_ID }} + private-key: ${{ secrets.TEMPORAL_CICD_PRIVATE_KEY }} + permission-contents: write + repositories: ${{ vars.DOWNSTREAM_REPO }} + + - name: Trigger ui-server workflow + uses: peter-evans/repository-dispatch@v3 + with: + token: ${{ steps.dispatch_token.outputs.token }} + repository: ${{ vars.DOWNSTREAM_ORG }}/${{ vars.DOWNSTREAM_REPO }} + event-type: sync-from-ui-commit + client-payload: | + { + "ref": "${{ github.sha }}" + } + + skip_notification: + needs: check_version_change + # Run if version validation failed + if: needs.check_version_change.outputs.version-ready == 'false' + + runs-on: ubuntu-latest + steps: + - name: Skip reason - Version validation failed + run: | + echo "## ℹ️ Release Draft Skipped" + echo "" + echo "**Reason:** Version validation failed" + echo "**Current Version:** ${{ needs.check_version_change.outputs.current-version }}" + echo "**Previous Version:** ${{ needs.check_version_change.outputs.previous-version }}" + echo "" + echo "Draft releases are only created when:" + echo "1. Both package.json and version.go have matching versions" + echo "2. The version has increased compared to the previous commit" + echo "" + echo "To create a release, use the Version Bump workflow:" + echo "Actions → Version Bump → Run workflow" diff --git a/.github/workflows/release-published.yml b/.github/workflows/release-published.yml new file mode 100644 index 000000000..99c2a57e4 --- /dev/null +++ b/.github/workflows/release-published.yml @@ -0,0 +1,62 @@ +name: Release Published + +on: + release: + types: [published] + +permissions: + contents: write + pull-requests: write + issues: write + actions: write + +jobs: + create_release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Validate release version + id: validate-release + uses: ./.github/actions/validate-published-release + with: + release-tag: ${{ github.event.release.tag_name }} + + - name: Setup Node + uses: ./.github/actions/setup-node + + - name: Build package + id: build-package + uses: ./.github/actions/build-and-package + + - name: upload package artifact + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ${{ steps.build-package.outputs.package-path }} + asset_name: temporal-ui-package.tar.gz + asset_content_type: application/gzip + + - name: Prepare dispatch token + id: dispatch_token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.TEMPORAL_CICD_APP_ID }} + private-key: ${{ secrets.TEMPORAL_CICD_PRIVATE_KEY }} + permission-contents: write + repositories: ${{ vars.DOWNSTREAM_REPO }} + + - name: Trigger ui-server workflow + uses: peter-evans/repository-dispatch@v3 + with: + token: ${{ steps.dispatch_token.outputs.token }} + repository: ${{ vars.DOWNSTREAM_ORG }}/${{ vars.DOWNSTREAM_REPO }} + event-type: sync-from-ui-release + client-payload: | + { + "ref": "refs/tags/${{ github.event.release.tag_name }}", + "release_tag": "${{ github.event.release.tag_name }}", + "release_url": "${{ github.event.release.html_url }}" + } diff --git a/.github/workflows/snapshot-tests.yml b/.github/workflows/snapshot-tests.yml deleted file mode 100644 index 27578e6cc..000000000 --- a/.github/workflows/snapshot-tests.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Snapshot Tests - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - snapshot-tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: pnpm/action-setup@v2.2.2 - with: - version: 7 - - name: Install modules - run: pnpm install - - name: Run snapshot tests - run: pnpm snapshot diff --git a/.github/workflows/storybook-tests.yml b/.github/workflows/storybook-tests.yml new file mode 100644 index 000000000..d11b3ded7 --- /dev/null +++ b/.github/workflows/storybook-tests.yml @@ -0,0 +1,17 @@ +name: Storybook Tests +on: deployment_status +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + if: github.event.deployment_status.state == 'success' + steps: + - uses: actions/checkout@v4 + - name: Checkout and Setup Node + uses: ./.github/actions/setup-node + - name: Install Playwright + run: pnpm exec playwright install --with-deps + - name: Run Storybook tests + run: pnpm stories:test + env: + TARGET_URL: '${{ github.event.deployment_status.target_url }}' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..4db5a2912 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,39 @@ +name: Server Test + +on: + push: + branches: [main] + pull_request: + branches: [main, 'codefreeze-*'] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Checkout and setup Node + uses: ./.github/actions/setup-node + - name: Build UI + run: pnpm build:server + + - uses: actions/setup-go@v3 + with: + go-version-file: server/go.mod + cache-dependency-path: server/go.sum + cache: true + check-latest: true + - name: Set up Protoc + uses: arduino/setup-protoc@v1 + with: + version: '3.x' + repo-token: ${{ secrets.GITHUB_TOKEN }} + + - name: build + working-directory: server + run: make build + + - name: test + working-directory: server + run: make test diff --git a/.github/workflows/trigger-ui-server-update.yml b/.github/workflows/trigger-ui-server-update.yml deleted file mode 100644 index 3a79ed521..000000000 --- a/.github/workflows/trigger-ui-server-update.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: 'Trigger ui-server Update' - -on: - push: - branches: - - main - workflow_dispatch: - -jobs: - notify: - name: 'trigger ui-server update' - runs-on: ubuntu-latest - - defaults: - run: - shell: bash - - steps: - - name: Dispatch ui-server Github Action - env: - PAT: ${{ secrets.COMMANDER_DATA_TOKEN }} - PARENT_REPO: temporalio/ui-server - PARENT_BRANCH: main - WORKFLOW_ID: update-ui.yml - run: | - curl -fL -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ env.PAT }}" https://api.github.com/repos/${{ env.PARENT_REPO }}/actions/workflows/${{ env.WORKFLOW_ID }}/dispatches -d '{"ref":"${{ env.PARENT_BRANCH }}"}' diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml deleted file mode 100644 index 2f84d4e74..000000000 --- a/.github/workflows/unit-tests.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Unit Tests - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: pnpm/action-setup@v2.2.2 - with: - version: 7 - - name: Install modules - run: pnpm install - - name: Populate .svelte-dir with files for vitest - run: pnpm run build:local - - name: Run tests - run: pnpm test diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml new file mode 100644 index 000000000..09b7b40b6 --- /dev/null +++ b/.github/workflows/version-bump.yml @@ -0,0 +1,165 @@ +name: Version Bump + +on: + workflow_dispatch: + inputs: + mode: + description: 'Version bump mode' + required: true + default: 'auto' + type: choice + options: + - auto + - manual + - dry-run + version_type: + description: 'Version type (used in manual mode)' + required: false + default: 'patch' + type: choice + options: + - major + - minor + - patch + specific_version: + description: 'Specific version to set (e.g., 2.38.0) - overrides version_type' + required: false + type: string + force_update: + description: 'Force update (override validation checks)' + required: false + default: false + type: boolean + +permissions: + contents: write + issues: write + pull-requests: write + actions: write + +jobs: + version-bump: + runs-on: ubuntu-latest + outputs: + version-changed: ${{ steps.calculate-version.outputs.version-changed }} + new-version: ${{ steps.calculate-version.outputs.new-version }} + pr-number: ${{ steps.create-pr.outputs.pull-request-number }} + pr-url: ${{ steps.create-pr.outputs.pull-request-url }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Validate current versions are in sync + id: validate-sync + uses: ./.github/actions/validate-version-sync + with: + force: ${{ inputs.force_update }} + + - name: Get last version tag + id: last-tag + run: | + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") + echo "last-tag=$LAST_TAG" >> $GITHUB_OUTPUT + echo "Last version tag: $LAST_TAG" + + - name: Analyze merged PRs since last version + id: analyze-prs + if: inputs.mode == 'auto' || inputs.mode == 'dry-run' + uses: ./.github/actions/analyze-version-bump-type + with: + last-tag: ${{ steps.last-tag.outputs.last-tag }} + + - name: Calculate new version + id: calculate-version + uses: ./.github/actions/calculate-version-bump + with: + current-version: ${{ steps.validate-sync.outputs.current-version }} + bump-type: ${{ inputs.mode == 'manual' && inputs.version_type || steps.analyze-prs.outputs.bump-type }} + specific-version: ${{ inputs.specific_version }} + mode: ${{ inputs.mode }} + + - name: Dry run summary + if: inputs.mode == 'dry-run' + run: | + echo "## 🔍 Dry Run Results" + echo "" + echo "**Current Version:** ${{ steps.validate-sync.outputs.current-version }}" + echo "**New Version:** ${{ steps.calculate-version.outputs.new-version }}" + if [[ -n "${{ inputs.specific_version }}" ]]; then + echo "**Version Source:** Specific version (manual override)" + echo "**Specified Version:** ${{ inputs.specific_version }}" + else + echo "**Version Source:** ${{ inputs.mode == 'manual' && 'Manual bump type' || 'Auto-detected from commits' }}" + echo "**Bump Type:** ${{ steps.analyze-prs.outputs.bump-type }}" + fi + echo "**Version Changed:** ${{ steps.calculate-version.outputs.version-changed }}" + echo "" + if [[ -z "${{ inputs.specific_version }}" ]]; then + echo "### 📝 Changes Since Last Version" + echo "${{ steps.analyze-prs.outputs.changelog }}" + echo "" + fi + echo "**Note:** This was a dry run. No files were modified." + + - name: Update version files + if: inputs.mode != 'dry-run' && steps.calculate-version.outputs.version-changed == 'true' + id: update-files + uses: ./.github/actions/update-version-files + with: + new-version: ${{ steps.calculate-version.outputs.new-version }} + + - name: Create Pull Request + if: inputs.mode != 'dry-run' && steps.calculate-version.outputs.version-changed == 'true' + id: create-pr + uses: peter-evans/create-pull-request@v7 + with: + commit-message: | + chore: bump version to ${{ steps.calculate-version.outputs.new-version }} + + Auto-generated version bump from ${{ steps.validate-sync.outputs.current-version }} to ${{ steps.calculate-version.outputs.new-version }} + + ${{ inputs.specific_version && format('Specific version: {0}', inputs.specific_version) || format('Bump type: {0}', inputs.mode == 'manual' && inputs.version_type || steps.analyze-prs.outputs.bump-type) }} + + ${{ inputs.specific_version && 'Manual version override' || format('Changes included:\n{0}', steps.analyze-prs.outputs.changelog || 'Manual version bump') }} + title: 'chore: bump version to ${{ steps.calculate-version.outputs.new-version }}' + body: | + ## 🚀 Version Bump to ${{ steps.calculate-version.outputs.new-version }} + + This PR updates the version from `${{ steps.validate-sync.outputs.current-version }}` to `${{ steps.calculate-version.outputs.new-version }}`. + + ### 📋 Files Updated + - `package.json` + - `server/server/version/version.go` + + ### 📝 Bump Details + ${{ inputs.specific_version && format('- **Version Source:** Specific version override\n- **Specified Version:** {0}', inputs.specific_version) || format('- **Bump Type:** {0}', inputs.mode == 'manual' && inputs.version_type || steps.analyze-prs.outputs.bump-type) }} + - **Mode:** ${{ inputs.mode }} + + ${{ inputs.specific_version && '### 📌 Manual Version Override\n\nThis version was manually specified rather than calculated from commit history.' || format('### 📖 Changes Since Last Version\n{0}', steps.analyze-prs.outputs.changelog || 'Manual version bump - no automatic changelog generated') }} + + --- + 🤖 This PR was automatically created by the Version Bump workflow. + branch: version-bump-${{ steps.calculate-version.outputs.new-version }} + delete-branch: true + reviewers: ${{ github.actor }} + committer: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com> + author: ${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com> + draft: always-true + + - name: Summary + if: inputs.mode != 'dry-run' + run: | + if [[ "${{ steps.calculate-version.outputs.version-changed }}" == "true" ]]; then + echo "🎉 Version bump completed!" + echo "- Old version: ${{ steps.validate-sync.outputs.current-version }}" + echo "- New version: ${{ steps.calculate-version.outputs.new-version }}" + if [[ -n "${{ steps.create-pr.outputs.pull-request-number }}" ]]; then + echo "- Pull Request: #${{ steps.create-pr.outputs.pull-request-number }}" + echo "- Pull Request URL: ${{ steps.create-pr.outputs.pull-request-url }}" + fi + else + echo "ℹ️ No version change was needed." + fi diff --git a/.gitignore b/.gitignore index 1159e54be..38809f2cf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,11 +2,23 @@ node_modules /.svelte-kit /package -/cypress/screenshots -/cypress/videos /coverage *.log *.swp /.vercel_build_output /.vercel -/.histoire/* +/build-local +/.history +/build +/bin +server/ui-server +server/api +server/ui/assets +/test-results/ +/playwright-report/ +/playwright/.cache/ +/dist +/audits +go.work +go.work.sum +.claude diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 000000000..145ac7e77 --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,6 @@ +{ + "*.{ts,js}": ["eslint --fix", "prettier --write"], + "*.{css,postcss}": ["stylelint --fix"], + "*.svelte": ["eslint --fix", "prettier --write", "stylelint --fix"], + "*.{json,md}": "prettier --write" +} diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..3c032078a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +18 diff --git a/.prettierignore b/.prettierignore index 21a9564ce..f86f9990f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,7 +1,15 @@ .svelte-kit/** -static/** build/** +e2e/test-results/** +e2e/playwright-report/** node_modules/** coverage/** +server/** .vercel/** /package +pnpm-lock.yaml +README.md +playwright-report +dist/**/* +tests/**/storageState.json +*.har diff --git a/.prettierrc b/.prettierrc index ef2d9081e..a03178aec 100644 --- a/.prettierrc +++ b/.prettierrc @@ -6,5 +6,6 @@ "singleQuote": true, "trailingComma": "all", "bracketSpacing": true, - "bracketSameLine": false + "bracketSameLine": false, + "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"] } diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 000000000..f30f00fd6 --- /dev/null +++ b/.storybook/main.ts @@ -0,0 +1,23 @@ +import type { StorybookConfig } from '@storybook/sveltekit'; + +const config: StorybookConfig = { + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(ts|svelte)'], + addons: [ + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-interactions', + { + name: '@storybook/addon-svelte-csf', + options: { + // REMOVE WHEN STORIES ARE UPGRADED TO NEW CSF SYNTAX + legacyTemplate: true, + }, + }, + '@storybook/addon-a11y', + '@storybook/addon-themes', + '@chromatic-com/storybook', + ], + framework: '@storybook/sveltekit', +}; + +export default config; diff --git a/.storybook/preview.ts b/.storybook/preview.ts new file mode 100644 index 000000000..72387bd02 --- /dev/null +++ b/.storybook/preview.ts @@ -0,0 +1,52 @@ +import type { Preview } from '@storybook/svelte'; + +import { withThemeByDataAttribute } from '@storybook/addon-themes'; + +import '../src/app.css'; + +import i18next from 'i18next'; + +import { i18nNamespaces } from '../src/lib/i18n'; +import resources from '../src/lib/i18n/locales'; + +i18next.init({ + fallbackLng: 'en', + load: 'languageOnly', + ns: i18nNamespaces, + defaultNS: 'common', + detection: { + order: ['querystring', 'localStorage', 'navigator'], + caches: ['localStorage'], + lookupQuerystring: 'lng', + lookupLocalStorage: 'locale', + }, + resources, +}); + +const preview: Preview = { + decorators: [ + withThemeByDataAttribute({ + defaultTheme: 'light', + themes: { + light: 'light', + dark: 'dark', + }, + attributeName: 'data-theme', + }), + ], + parameters: { + actions: { argTypesRegex: '^on[A-Z].*' }, + backgrounds: { + disable: true, + }, + controls: { + exclude: /^id|name|class|data-\w+|on\w+/, + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + }, +}; + +export default preview; diff --git a/.storybook/test-runner.ts b/.storybook/test-runner.ts new file mode 100644 index 000000000..b2839fcca --- /dev/null +++ b/.storybook/test-runner.ts @@ -0,0 +1,23 @@ +import type { TestRunnerConfig } from '@storybook/test-runner'; +import { getStoryContext } from '@storybook/test-runner'; +import { injectAxe, checkA11y } from 'axe-playwright'; + +const config: TestRunnerConfig = { + async preVisit(page) { + await injectAxe(page); + }, + async postVisit(page, context) { + const storyContext = await getStoryContext(page, context); + if (storyContext.parameters?.a11y?.disable) { + return; + } + await checkA11y(page, '#storybook-root', { + detailedReport: true, + detailedReportOptions: { + html: true, + }, + }); + }, +}; + +export default config; diff --git a/.stylelintrc b/.stylelintrc index bc31636d5..5035755e4 100644 --- a/.stylelintrc +++ b/.stylelintrc @@ -1,6 +1,14 @@ { - "extends": ["stylelint-config-recommended"], + "extends": ["stylelint-config-recommended", "stylelint-config-standard"], "processors": [], + "customSyntax": "postcss-html", + "ignoreFiles": [ + "node_modules/*", + "src/assets/**", + "build/**", + "server/**", + "src/lib/vendor/css/normalize.css" + ], "rules": { "at-rule-no-unknown": [ true, @@ -20,14 +28,29 @@ ] } ], - "declaration-block-trailing-semicolon": null, - "no-descending-specificity": [ + "function-no-unknown": [ true, { - "ignore": ["selectors-within-list"] + "ignoreFunctions": ["theme"] } ], - "no-invalid-double-slash-comments": true, - "block-no-empty": null + "custom-property-pattern": null, + "declaration-block-no-redundant-longhand-properties": [ + true, + { + "ignoreShorthands": ["border-radius"] + } + ], + "function-linear-gradient-no-nonstandard-direction": null, + "keyframes-name-pattern": null, + "no-descending-specificity": null, + "selector-class-pattern": null, + "selector-pseudo-class-no-unknown": [ + true, + { + "ignorePseudoClasses": ["global"] + } + ], + "value-keyword-case": null } } diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 000000000..e1010718d --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +pnpm 8.15.7 diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..d329f5ee6 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "bradlc.vscode-tailwindcss", + "svelte.svelte-vscode", + "yoavbls.pretty-ts-errors" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json index de1d6cf92..76595237f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,13 +7,25 @@ { "type": "node", "request": "launch", - "name": "Launch Program", + "name": "Start UI Development Server", "skipFiles": ["/**"], - "runtimeExecutable": "npm", - "runtimeArgs": ["run-script", "start"], - "env": { - "VITE_PUBLIC_PATH": "" - } + "runtimeExecutable": "pnpm", + "runtimeArgs": ["run", "start"], + "serverReadyAction": { + "action": "startDebugging", + "name": "Launch Browser", + "killOnServerStop": false + }, + "env": {} + }, + { + "name": "Debug Go server", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "server/cmd/server", + "cwd": "${workspaceFolder}/server", + "args": ["start"] } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 64f75dafd..5aee58a06 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,14 @@ "**/.vercel": true }, "editor.formatOnSave": true, + "eslint.validate": ["javascript", "typescript", "svelte"], "css.customData": [".vscode/css-custom-data.json"], - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "tailwindCSS.experimental.classRegex": [ + ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], + ["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] + ], + "files.associations": { + "*.css": "tailwindcss" + } } diff --git a/.vscode/temporal-ui.code-snippets b/.vscode/temporal-ui.code-snippets index e69de29bb..f2067b421 100644 --- a/.vscode/temporal-ui.code-snippets +++ b/.vscode/temporal-ui.code-snippets @@ -0,0 +1,7 @@ +{ + "Playwright Test": { + "prefix": "pwt", + "body": ["test('${1}', async ({ page }) => {", " $0", "});"], + "description": "useState()", + }, +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..5dbf7275a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,82 @@ +# Claude AI Assistant Rules for Temporal UI + +SvelteKit + Svelte 5 + TypeScript + TailwindCSS + Holocene design system + +## Commands + +```bash +pnpm lint # Run all linters +pnpm check # TypeScript type checking +pnpm test # Run unit tests +``` + +## Svelte 5 Patterns + +```typescript +// Props +let { class: className = '', adapter }: Props = $props(); + +// State +let count = $state(0); + +// Computed +const doubled = $derived(count * 2); + +// Effects +$effect(() => { + console.log('Count:', count); + return () => cleanup(); +}); + +// SuperForms +const { form, errors, enhance } = $derived( + superForm(data, { + SPA: true, + validators: zodClient(schema), + onUpdate: async ({ form }) => { + /* handle submit */ + }, + }), +); +``` + +## Import Order + +1. Node.js built-ins +2. External libraries (with `svelte/**` first) +3. SvelteKit imports (`$app/**`, `$types`) +4. Internal imports (`$lib/**`) +5. Component imports (`$components/**/*.svelte`) +6. Relative imports (`./`, `../`) + +## Workflow + +1. **Always run linting**: Execute `pnpm lint` after making changes +2. **Type checking**: Run `pnpm check` to verify TypeScript compliance +3. **Test execution**: Run appropriate test suites based on changes +4. **Follow patterns**: Use existing component patterns and utility functions +5. **Design system**: Prefer Holocene components over custom implementations +6. **Accessibility**: Ensure proper ARIA attributes and semantic HTML + +## Code Generation + +- **No comments**: Don't add code comments unless explicitly requested +- **Type safety**: Always provide proper TypeScript types +- **Component reuse**: Leverage existing components and utilities +- **Test coverage**: Write tests for new utilities and business logic +- **Import organization**: Follow the established import order + +## Error Handling + +- **Validation**: Use Zod for runtime type validation +- **Error boundaries**: Implement proper error boundaries for components +- **Network errors**: Handle API failures gracefully +- **User feedback**: Provide clear error messages and loading states + +## Naming + +- **Files**: kebab-case (`workflow-status.svelte`) +- **Components**: PascalCase in imports, kebab-case for files +- **Functions**: camelCase +- **Types**: PascalCase +- **Use** `import type` for type-only imports diff --git a/README.md b/README.md index 269cd8451..3ffc697e1 100644 --- a/README.md +++ b/README.md @@ -2,112 +2,215 @@ ## Prerequisites -Temporal must be running in development. (For details, see [Run a dev Cluster](https://docs.temporal.io/application-development-guide#run-a-dev-cluster) in the documentation.) +Temporal must be running in development. Temporal UI requires [Temporal v1.16.0](https://github.com/temporalio/temporal/releases/tag/v1.16.0) or later. -## Trying it out +Make sure [Corepack](https://pnpm.io/installation#using-corepack) is installed and run `corepack enable pnpm`. -After pulling down the lastest version of Temporal's [`docker-compose`](https://github.com/temporalio/docker-compose), you can access the UI by visiting `http://localhost:8080`. +### Using Temporal CLI + +You can install [Temporal CLI][] using [Homebrew][]: + +```sh +brew install temporal +``` + +You can start a Temporal server in development using the following command: + +```sh +temporal server start-dev +``` + +[temporal cli]: https://github.com/temporalio/cli +[homebrew]: https://brew.sh -## Trying it out: Bleeding edge +## Development -Starting the UI API server will give you a somewhat recent version on `localhost:8080`. If you want to use the most recent commit to `main`, you can spin up a bleeding-edge build as described below. +### Local Development + +#### Setup Once you have the prerequisites going, run the following: ```bash pnpm install -pnpm run build:local -pnpm run preview:local ``` -## Developing +Running `pnpm install` will attempt to download and install the most recent version of [Temporal CLI][] into `./bin/cli/temporal`. The development server will attempt to use use this version of this Temporal when starting up. -Developing the UI has the same prequisites as trying it out. Once you've created a project and installed dependencies with `pnpm install`, start the development server: +- If that port is already in use, the UI will fallback to trying to talk to whatever process is running on that port. +- If you do not have a version of Temporal CLI at `./bin/cli/temporal`, the development server will look for a version of Temporal CLI in your path. +- For Windows users, you will need to start Temporal using one of the methods listed above until we have sufficiently tested this functionality on Windows. (We would absolutely welcome a pull request.) ```bash -pnpm start +git submodule update ``` -and open [`localhost:3000`](http://localhost:3000). +This clones the [Temporal API Protos](https://github.com/temporalio/api) into the git submodule, which is required for local development of the UI when running against a local version of the UI server. + -By default, the application will start up with a version of the UI for the local version of Temporal. You can start the UI for Temporal Cloud by setting the `VITE_TEMPORAL_UI_BUILD_TARGET` target to `cloud`. Alternatively, you can use either of the following scripts: +To run a local development version of the Svelte application via Vite, run `pnpm dev`. The application will run on [http://localhost:3000]() against a local ui-server running along with Temporal server from the temporal-cli. ```bash -pnpm run dev:local -pnpm run dev:cloud +pnpm dev ``` -## Building - -The Temporal UI _must_ be built for either the local version or Temporal Cloud. You must set the `VITE_TEMPORAL_UI_BUILD_TARGET` environment variable in order to build the assets. This will be set for you if you use either of the following `pnpm` scripts. - +Alternatively, you can run `pnpm dev:temporal-cli` to run against the version of ui-server and Temporal server included temporal-cli. ```bash -pnpm run build:local -pnpm run build:cloud +pnpm dev:temporal-cli ``` -The resulting assets will be placed in `.vercel/output/static`. +### Building the UI + +The Temporal UI can be built for local preview. You must set the `VITE_TEMPORAL_UI_BUILD_TARGET` environment variable in order to build the assets. This will be set for you if you use either of the following `pnpm` scripts. The resulting assets will be placed in `./dist`. > You can preview the built app with `pnpm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production. -## Configuration -Set these environment variables if you want to change their defaults +```bash +pnpm build:local +``` -| Variable | Description | Default | Stage | -| --------- | ---------------------------------------------------------------- | --------------------- | ----- | -| VITE_API | Temporal HTTP API address. Set to empty `` to use relative paths | http://localhost:8080 | Build | -| VITE_MODE | Build target | development | Build | -## Developing with Canary +The Temporal UI can build assets for ui-server. The resulting assets will be placed in `./server/ui/assets`. -To get a better representation of production data, you can run our UI with the [canary-go](https://github.com/temporalio/canary-go) repo. You will need go installed on your machine. +```bash +pnpm build:server +``` -### canary-go +### Using Docker + +After pulling down the lastest version of Temporal's [`docker-compose`](https://github.com/temporalio/docker-compose), you can access the UI by visiting `http://localhost:8080`. + +If you want to point the development environment at the `docker-compose` version of Temporal, you can use the following command: ```bash -make bins -./temporal-canary start +pnpm dev:docker ``` -### temporal - ```bash -make bins -TEMPORAL_ENVIRONMENT=development_sqlite make start +pnpm run build:docker +pnpm run preview:docker ``` -### tctl -```bash -make build -./tctl config set version next -./tctl -n canary namespace register -./tctl -n default namespace register -./tctl cluster add-search-attributes -y \ - --name CustomKeywordField --type Keyword \ - --name CustomStringField --type Text \ - --name CustomTextField --type Text \ - --name CustomIntField --type Int \ - --name CustomDatetimeField --type Datetime \ - --name CustomDoubleField --type Double \ - --name CustomBoolField --type Bool +### Running UI and Temporal Server locally + +1. In `temporal` repo, run Temporal server locally + +```diff +make start ``` -To view the search attributes code: -https://github.com/temporalio/docker-builds/blob/main/docker/auto-setup.sh#L297 +2. In `ui` repo, add .env.local-temporal file with the following env variables -### ui-server +```diff +VITE_TEMPORAL_PORT="7134" +VITE_API="http://localhost:8081" +VITE_MODE="development" +VITE_TEMPORAL_UI_BUILD_TARGET="local" +``` -```bash -make build-server -./ui-server start +3. Run UI with pnpm dev:local-temporal + +```diff +pnpm dev:local-temporal ``` -### ui +4. Create namespace with CLI (Temporal server does not do this automatically unlike CLI) -```bash -pnpm start +```diff +temporal operator namespace create default ``` + +## OSS API Development + +### Understanding OSS API Endpoints + +When developing new features that require API endpoints for the OSS version of Temporal, the available APIs can be found in the [Temporal API repository](https://github.com/temporalio/api). These gRPC APIs are converted to HTTP through the ui-server's gRPC proxy. + +**Key Resources:** +- **API Definitions**: https://github.com/temporalio/api/tree/master/temporal/api +- **Service Definitions**: Look for `*_service.proto` files for available operations +- **HTTP Conversion**: The ui-server automatically converts gRPC calls to HTTP endpoints + +**Common API Patterns:** +- **WorkflowService**: `temporal/api/workflowservice/v1/service.proto` - Core workflow operations +- **OperatorService**: `temporal/api/operatorservice/v1/service.proto` - Administrative operations (search attributes, clusters, etc.) +- **Endpoint Mapping**: gRPC service methods map to HTTP POST endpoints at `/api/v1/{service}/{method}` + +**Example:** +- gRPC: `temporal.api.operatorservice.v1.OperatorService.ListSearchAttributes` +- HTTP: `POST /api/v1/operator/list-search-attributes` + +**Finding Available Operations:** +1. Browse the API repository to find relevant service files +2. Look for existing method definitions in `*_service.proto` files +3. Check if the operation you need already exists before requesting new endpoints +4. For new operations, coordinate with the SDK team to add them to the appropriate service + +**Development Workflow:** +1. Check existing API definitions for required operations +2. Use existing endpoints where possible via services in `src/lib/services/` +3. For missing endpoints, create adapters with TODO comments (like in `SearchAttributesAdapter`) +4. Coordinate with SDK team to implement missing gRPC methods +5. Update adapters once endpoints are available + +## Testing +We use [Playwright](https://playwright.dev) to interactively test the Temporal UI. + +### Running the E2E tests +The e2e tests run against the UI with workflows via the [TypeScript SDK](https://github.com/temporalio/sdk-typescript), a locally built version of the UI Server, a NodeJS/Express Codec Server, and a Temporal dev server via [Temporal CLI](https://github.com/temporalio/cli) + +`pnpm test:e2e` + +### Running the Integration tests +The integration tests run against the UI using Mocks + +`pnpm test:integration` + +Both `pnpm test:e2e` and `pnpm test:integration` use the `playwright.config.ts` at the root of the repo. This file will [run the UI via the vite development server](https://playwright.dev/docs/api/class-testconfig#test-config-web-server) with the correct configuration by running either `pnpm serve:playwright:e2e` or `pnpm serve:playwright:integration`. It will also invoke the default function in `tests/globalSetup.ts`, which instantiates all of the necessary dependencies (UI Server, Codec Server, Temporal Server, Temporl Workers, etc.) when running in e2e mode. + +## Configuration + +Set these environment variables if you want to change their defaults + +| Variable | Description | Default | Stage | +| --------- | ---------------------------------------------------------------- | --------------------- | ----- | +| VITE_API | Temporal HTTP API address. Set to empty `` to use relative paths | http://localhost:8322 | Build | +| VITE_MODE | Build target | development | Build | +| UI_SERVER_VERBOSE | Enable verbose output to see Air build logs and server output for debugging | false | Development | +| UI_SERVER_HOT_RELOAD | Enable hot reload using Air | false | Development | + +## Releases + +This repository uses an automated release management system that enforces version bump PRs before releases and maintains dual version sync between `package.json` and `server/server/version/version.go`. + +### Release Management + +The release system uses custom GitHub Actions for modular, reusable functionality. See [GitHub Workflows Documentation](.github/WORKFLOWS.md) for detailed information about the 8 custom actions and 3 workflows. + +**Release Process**: +1. **Version Bump**: Use Actions → "Version Bump" to create a PR with updated versions + - **Auto mode**: Analyzes commits since last tag for semantic versioning + - **Manual mode**: Specify major/minor/patch bump type + - **Specific version**: Override with exact version (e.g., "2.38.0") + - **Dry run**: Preview changes without making modifications +2. **Review and Merge**: Review the auto-generated version bump PR and merge to main +3. **Draft Release**: Automatically created when version changes are detected +4. **Publish Release**: Review and publish the auto-generated draft release +5. **UI-server Release**: A published release automatically triggers a matching release in the ui-server repository + +**Version Source of Truth**: The Go `UIVersion` constant in `server/server/version/version.go` is the authoritative source. All validation uses this as the reference, and `package.json` must be kept in sync. + +**Version Validation**: +- Run `pnpm validate:versions` to ensure version files are in sync and ready for release +- Validation compares against last git tag (not last commit) for robust release workflows +- Custom actions provide detailed validation and error messages + +**Integration**: +- Draft releases trigger downstream ui-server releases and Docker image publishing +- UI repo releases (https://github.com/temporalio/ui/releases) contain the latest UI artifacts +- Our [npm package](https://www.npmjs.com/package/@temporalio/ui) will be manually published as needed. +- UI-server repo releases (https://github.com/temporalio/ui-server/releases) manage Docker images diff --git a/chromatic.config.json b/chromatic.config.json new file mode 100644 index 000000000..4b350ac11 --- /dev/null +++ b/chromatic.config.json @@ -0,0 +1,9 @@ +{ + "autoAcceptChanges": "main", + "buildScriptName": "stories:build", + "exitOnceUploaded": true, + "exitZeroOnChanges": true, + "externals": ["public/**"], + "onlyChanged": true, + "skip": "dependabot/**" +} diff --git a/cypress.json b/cypress.json deleted file mode 100644 index 31e18b08f..000000000 --- a/cypress.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "baseUrl": "http://localhost:3000", - "projectId": "g7xfxw", - "env": { - "VITE_API_HOST": "http://localhost:8080", - "VITE_MODE": "test", - "VITE_TEMPORAL_UI_BUILD_TARGET": "local" - }, - "viewportWidth": 1280 -} diff --git a/cypress/fixtures/activity-task-queues.json b/cypress/fixtures/activity-task-queues.json deleted file mode 100644 index db5ba28e8..000000000 --- a/cypress/fixtures/activity-task-queues.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "pollers": [ - { - "lastAccessTime": "2022-05-05T21:42:46.576609378Z", - "identity": "@poller", - "ratePerSecond": 100000 - } - ], - "taskQueueStatus": null -} diff --git a/cypress/fixtures/archived-namespace.json b/cypress/fixtures/archived-namespace.json deleted file mode 100644 index 90bef652e..000000000 --- a/cypress/fixtures/archived-namespace.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "namespaceInfo": { - "name": "some-other-namespace", - "state": "Registered", - "description": "", - "ownerEmail": "", - "data": {}, - "id": "5411056f-9bd0-4b4e-90fa-e88e3031a0d0" - }, - "config": { - "workflowExecutionRetentionTtl": "259200s", - "badBinaries": { - "binaries": {} - }, - "historyArchivalState": "Enabled", - "historyArchivalUri": "", - "visibilityArchivalState": "Enabled", - "visibilityArchivalUri": "" - }, - "replicationConfig": { - "activeClusterName": "active", - "clusters": [ - { - "clusterName": "active" - } - ], - "state": "Unspecified" - }, - "failoverVersion": "0", - "isGlobalNamespace": false -} diff --git a/cypress/fixtures/batch-operation-status.json b/cypress/fixtures/batch-operation-status.json deleted file mode 100644 index a1fffdaaf..000000000 --- a/cypress/fixtures/batch-operation-status.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "operationType": "Terminate", - "jobId": "6216d7c6-c987-4b27-a854-3bb9c72b3bbb", - "state": "Completed", - "startTime": "2022-11-01T00:00:00.000000000Z", - "closeTime": "2022-11-01T00:00:00.000000000Z", - "totalOperationCount": "10", - "completeOperationCount": "10", - "failureOperationCount": "0", - "identity": "", - "reason": "" -} diff --git a/cypress/fixtures/cluster-server-without-reserve-event-sorting.json b/cypress/fixtures/cluster-server-without-reserve-event-sorting.json deleted file mode 100644 index 523d8bce4..000000000 --- a/cypress/fixtures/cluster-server-without-reserve-event-sorting.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "supportedClients": { - "temporal-cli": "\u003c2.0.0", - "temporal-go": "\u003c2.0.0", - "temporal-java": "\u003c2.0.0", - "temporal-php": "\u003c2.0.0", - "temporal-server": "\u003c2.0.0", - "temporal-typescript": "\u003c2.0.0" - }, - "serverVersion": "1.14.0", - "clusterId": "fe325f09-1d00-45c0-8924-ce6f0e465258", - "versionInfo": { - "current": { - "version": "1.14.0", - "releaseTime": "2022-03-04T23:00:00Z", - "notes": "" - }, - "recommended": { - "version": "1.17.1", - "releaseTime": "2022-04-19T19:50:00Z", - "notes": "Temporal 1.16.1" - }, - "instructions": "", - "alerts": [ - { - "message": "🪐 A new release is available!", - "severity": "Low" - } - ], - "lastUpdateTime": "2022-05-06T15:58:56.815957932Z" - }, - "clusterName": "active", - "historyShardCount": 4 -} diff --git a/cypress/fixtures/cluster-with-elasticsearch.json b/cypress/fixtures/cluster-with-elasticsearch.json deleted file mode 100644 index 6072a1135..000000000 --- a/cypress/fixtures/cluster-with-elasticsearch.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "supportedClients": { - "temporal-cli": "\u003c2.0.0", - "temporal-go": "\u003c2.0.0", - "temporal-java": "\u003c2.0.0", - "temporal-php": "\u003c2.0.0", - "temporal-server": "\u003c2.0.0", - "temporal-typescript": "\u003c2.0.0", - "temporal-ui": "\u003c3.0.0" - }, - "serverVersion": "1.18.4", - "clusterId": "17235226-f862-4d2a-a809-898c52dc8ab4", - "versionInfo": { - "current": { - "version": "1.18.4", - "releaseTime": "2022-10-31T22:14:46Z", - "notes": "" - }, - "recommended": { - "version": "1.18.4", - "releaseTime": "2022-10-31T22:14:46Z", - "notes": "" - }, - "instructions": "", - "alerts": [ - { - "message": "🪐 A new release is available!", - "severity": "Low" - } - ], - "lastUpdateTime": "2022-11-09T22:29:04.612659335Z" - }, - "clusterName": "active", - "historyShardCount": 4, - "persistenceStore": "postgres", - "visibilityStore": "postgres,elasticsearch" -} diff --git a/cypress/fixtures/cluster.json b/cypress/fixtures/cluster.json deleted file mode 100644 index 7a496f88b..000000000 --- a/cypress/fixtures/cluster.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "supportedClients": { - "temporal-cli": "\u003c2.0.0", - "temporal-go": "\u003c2.0.0", - "temporal-java": "\u003c2.0.0", - "temporal-php": "\u003c2.0.0", - "temporal-server": "\u003c2.0.0", - "temporal-typescript": "\u003c2.0.0" - }, - "serverVersion": "1.16.2", - "clusterId": "fe325f09-1d00-45c0-8924-ce6f0e465258", - "versionInfo": { - "current": { - "version": "1.16.2", - "releaseTime": "2022-03-04T23:00:00Z", - "notes": "" - }, - "recommended": { - "version": "1.17.1", - "releaseTime": "2022-04-19T19:50:00Z", - "notes": "Temporal 1.16.1" - }, - "instructions": "", - "alerts": [ - { - "message": "🪐 A new release is available!", - "severity": "Low" - } - ], - "lastUpdateTime": "2022-05-06T15:58:56.815957932Z" - }, - "clusterName": "active", - "historyShardCount": 4 -} diff --git a/cypress/fixtures/count.json b/cypress/fixtures/count.json deleted file mode 100644 index 18b01584b..000000000 --- a/cypress/fixtures/count.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "count": 15 -} diff --git a/cypress/fixtures/empty-task-queues.json b/cypress/fixtures/empty-task-queues.json deleted file mode 100644 index 4af7433aa..000000000 --- a/cypress/fixtures/empty-task-queues.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "pollers": [], - "taskQueueStatus": null -} diff --git a/cypress/fixtures/event-history-canceled.json b/cypress/fixtures/event-history-canceled.json deleted file mode 100644 index 037c61e6a..000000000 --- a/cypress/fixtures/event-history-canceled.json +++ /dev/null @@ -1,213 +0,0 @@ -{ - "history": { - "events": [ - { - "eventId": "1", - "eventTime": "2022-04-28T05:30:19.449978841Z", - "eventType": "WorkflowExecutionStarted", - "version": "0", - "taskId": "1370960", - "workflowExecutionStartedEventAttributes": { - "workflowType": { - "name": "RainbowStatusesWorkflow" - }, - "parentWorkflowNamespace": "", - "parentWorkflowExecution": null, - "parentInitiatedEventId": "0", - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "input": { - "payloads": [ - { - "metadata": { - "encoding": "anNvbi9wbGFpbg==" - }, - "data": "NA==" - } - ] - }, - "workflowExecutionTimeout": "0s", - "workflowRunTimeout": "0s", - "workflowTaskTimeout": "10s", - "continuedExecutionRunId": "", - "initiator": "Unspecified", - "continuedFailure": null, - "lastCompletionResult": null, - "originalExecutionRunId": "4204e11a-f7b5-44c5-b8d4-eeb88580daf4", - "identity": "168773@user0@", - "firstExecutionRunId": "4204e11a-f7b5-44c5-b8d4-eeb88580daf4", - "retryPolicy": null, - "attempt": 1, - "workflowExecutionExpirationTime": null, - "cronSchedule": "", - "firstWorkflowTaskBackoff": "0s", - "memo": null, - "searchAttributes": { - "indexedFields": { - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "ZmFsc2U=" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDQtMjhUMDU6MzA6MTkuNDQ5MzAxNDg3WiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "Mw==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "Mw==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtZDQ5NGM0Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgZDQ5NGM0IENhbmNlbGVkIg==" - } - } - }, - "prevAutoResetPoints": null, - "header": { - "fields": {} - } - } - }, - { - "eventId": "2", - "eventTime": "2022-04-28T05:30:19.449996565Z", - "eventType": "WorkflowTaskScheduled", - "version": "0", - "taskId": "1370961", - "workflowTaskScheduledEventAttributes": { - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "startToCloseTimeout": "10s", - "attempt": 1 - } - }, - { - "eventId": "3", - "eventTime": "2022-04-28T05:30:19.456915704Z", - "eventType": "WorkflowExecutionCancelRequested", - "version": "0", - "taskId": "1370965", - "workflowExecutionCancelRequestedEventAttributes": { - "cause": "", - "externalInitiatedEventId": "0", - "externalWorkflowExecution": null, - "identity": "168773@user0@" - } - }, - { - "eventId": "4", - "eventTime": "2022-04-28T05:30:19.460113854Z", - "eventType": "WorkflowTaskStarted", - "version": "0", - "taskId": "1370967", - "workflowTaskStartedEventAttributes": { - "scheduledEventId": "2", - "identity": "168631@user0@", - "requestId": "ebc4aa28-4235-4d22-9c3d-b9cfbabc771e" - } - }, - { - "eventId": "5", - "eventTime": "2022-04-28T05:30:19.466253747Z", - "eventType": "WorkflowTaskCompleted", - "version": "0", - "taskId": "1370970", - "workflowTaskCompletedEventAttributes": { - "scheduledEventId": "2", - "startedEventId": "4", - "identity": "168631@user0@", - "binaryChecksum": "04f0fb34cfd90d692fa1d506c626a598" - } - }, - { - "eventId": "6", - "eventTime": "2022-04-28T05:30:19.466279986Z", - "eventType": "ActivityTaskScheduled", - "version": "0", - "taskId": "1370971", - "activityTaskScheduledEventAttributes": { - "activityId": "6", - "activityType": { - "name": "LongActivity" - }, - "namespace": "", - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "header": { - "fields": {} - }, - "input": null, - "scheduleToCloseTimeout": "0s", - "scheduleToStartTimeout": "0s", - "startToCloseTimeout": "3600s", - "heartbeatTimeout": "0s", - "workflowTaskCompletedEventId": "5", - "retryPolicy": { - "initialInterval": "1s", - "backoffCoefficient": 2, - "maximumInterval": "100s", - "maximumAttempts": 1, - "nonRetryableErrorTypes": [] - } - } - }, - { - "eventId": "7", - "eventTime": "2022-04-28T05:30:19.466286838Z", - "eventType": "ActivityTaskCancelRequested", - "version": "0", - "taskId": "1370972", - "activityTaskCancelRequestedEventAttributes": { - "scheduledEventId": "6", - "workflowTaskCompletedEventId": "5" - } - }, - { - "eventId": "8", - "eventTime": "2022-04-28T05:30:19.466292877Z", - "eventType": "WorkflowExecutionCanceled", - "version": "0", - "taskId": "1370973", - "workflowExecutionCanceledEventAttributes": { - "workflowTaskCompletedEventId": "5", - "details": null - } - } - ] - }, - "rawHistory": [], - "nextPageToken": null, - "archived": false -} diff --git a/cypress/fixtures/event-history-completed-null.json b/cypress/fixtures/event-history-completed-null.json deleted file mode 100644 index a31c3cbf3..000000000 --- a/cypress/fixtures/event-history-completed-null.json +++ /dev/null @@ -1,257 +0,0 @@ -{ - "history": { - "events": [ - { - "eventId": "1", - "eventTime": "2022-04-11T12:59:24.522874755Z", - "eventType": "WorkflowExecutionStarted", - "version": "0", - "taskId": "1048675", - "workflowExecutionStartedEventAttributes": { - "workflowType": { - "name": "RainbowStatusesWorkflow" - }, - "parentWorkflowNamespace": "", - "parentWorkflowExecution": null, - "parentInitiatedEventId": "0", - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "input": { - "payloads": [ - { - "metadata": { - "encoding": "anNvbi9wbGFpbg==" - }, - "data": "Mg==" - } - ] - }, - "workflowExecutionTimeout": "0s", - "workflowRunTimeout": "0s", - "workflowTaskTimeout": "10s", - "continuedExecutionRunId": "", - "initiator": "Unspecified", - "continuedFailure": null, - "lastCompletionResult": null, - "originalExecutionRunId": "db7b0929-24bc-424c-a935-a1f8da69755e", - "identity": "57997@MacBook-Pro@", - "firstExecutionRunId": "db7b0929-24bc-424c-a935-a1f8da69755e", - "retryPolicy": null, - "attempt": 1, - "workflowExecutionExpirationTime": null, - "cronSchedule": "", - "firstWorkflowTaskBackoff": "0s", - "memo": null, - "searchAttributes": { - "indexedFields": { - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "ZmFsc2U=" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDQtMTFUMTI6NTk6MjQuNTIzMzA5WiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "MQ==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "MQ==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtYjEyNDUzIg==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgYjEyNDUzIENvbXBsZXRlZCI=" - } - } - }, - "prevAutoResetPoints": null, - "header": { - "fields": {} - } - } - }, - { - "eventId": "2", - "eventTime": "2022-04-11T12:59:24.522889797Z", - "eventType": "WorkflowTaskScheduled", - "version": "0", - "taskId": "1048676", - "workflowTaskScheduledEventAttributes": { - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "startToCloseTimeout": "10s", - "attempt": 1 - } - }, - { - "eventId": "3", - "eventTime": "2022-04-11T12:59:24.544864255Z", - "eventType": "WorkflowTaskStarted", - "version": "0", - "taskId": "1048690", - "workflowTaskStartedEventAttributes": { - "scheduledEventId": "2", - "identity": "57979@MacBook-Pro@", - "requestId": "1e90c204-0535-462c-b335-4052d3658e02" - } - }, - { - "eventId": "4", - "eventTime": "2022-04-11T12:59:24.554243505Z", - "eventType": "WorkflowTaskCompleted", - "version": "0", - "taskId": "1048702", - "workflowTaskCompletedEventAttributes": { - "scheduledEventId": "2", - "startedEventId": "3", - "identity": "57979@MacBook-Pro@", - "binaryChecksum": "32e04259e3a1e137fa66893bb617979a" - } - }, - { - "eventId": "5", - "eventTime": "2022-04-11T12:59:24.554351380Z", - "eventType": "ActivityTaskScheduled", - "version": "0", - "taskId": "1048703", - "activityTaskScheduledEventAttributes": { - "activityId": "5", - "activityType": { - "name": "CompletedActivity" - }, - "namespace": "", - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "header": { - "fields": {} - }, - "input": null, - "scheduleToCloseTimeout": "0s", - "scheduleToStartTimeout": "0s", - "startToCloseTimeout": "3600s", - "heartbeatTimeout": "0s", - "workflowTaskCompletedEventId": "4", - "retryPolicy": { - "initialInterval": "1s", - "backoffCoefficient": 2, - "maximumInterval": "100s", - "maximumAttempts": 1, - "nonRetryableErrorTypes": [] - } - } - }, - { - "eventId": "6", - "eventTime": "2022-04-11T12:59:24.581489380Z", - "eventType": "ActivityTaskStarted", - "version": "0", - "taskId": "1048729", - "activityTaskStartedEventAttributes": { - "scheduledEventId": "5", - "identity": "57979@MacBook-Pro@", - "requestId": "ad1409a9-0f7c-4524-9612-dc96d8d3cb92", - "attempt": 1, - "lastFailure": null - } - }, - { - "eventId": "7", - "eventTime": "2022-04-11T12:59:24.596869172Z", - "eventType": "ActivityTaskCompleted", - "version": "0", - "taskId": "1048730", - "activityTaskCompletedEventAttributes": { - "result": null, - "scheduledEventId": "5", - "startedEventId": "6", - "identity": "57979@MacBook-Pro@" - } - }, - { - "eventId": "8", - "eventTime": "2022-04-11T12:59:24.596873172Z", - "eventType": "WorkflowTaskScheduled", - "version": "0", - "taskId": "1048731", - "workflowTaskScheduledEventAttributes": { - "taskQueue": { - "name": "MacBook-Pro:2ed93de6-ee9e-4d83-bde1-9d08303b992c", - "kind": "Sticky" - }, - "startToCloseTimeout": "10s", - "attempt": 1 - } - }, - { - "eventId": "9", - "eventTime": "2022-04-11T12:59:24.607690588Z", - "eventType": "WorkflowTaskStarted", - "version": "0", - "taskId": "1048741", - "workflowTaskStartedEventAttributes": { - "scheduledEventId": "8", - "identity": "57979@MacBook-Pro@", - "requestId": "9c6532d7-a87c-4f3b-ac87-b91748b97906" - } - }, - { - "eventId": "10", - "eventTime": "2022-04-11T12:59:24.614579172Z", - "eventType": "WorkflowTaskCompleted", - "version": "0", - "taskId": "1048744", - "workflowTaskCompletedEventAttributes": { - "scheduledEventId": "8", - "startedEventId": "9", - "identity": "57979@MacBook-Pro@", - "binaryChecksum": "32e04259e3a1e137fa66893bb617979a" - } - }, - { - "eventId": "11", - "eventTime": "2022-04-11T12:59:24.614591797Z", - "eventType": "WorkflowExecutionCompleted", - "version": "0", - "taskId": "1048745", - "workflowExecutionCompletedEventAttributes": { - "result": null, - "workflowTaskCompletedEventId": "10", - "newExecutionRunId": "" - } - } - ] - }, - "rawHistory": [], - "nextPageToken": null, - "archived": false -} diff --git a/cypress/fixtures/event-history-completed-reverse.json b/cypress/fixtures/event-history-completed-reverse.json deleted file mode 100644 index f0e82a450..000000000 --- a/cypress/fixtures/event-history-completed-reverse.json +++ /dev/null @@ -1,303 +0,0 @@ -{ - "history": { - "events": [ - { - "eventId": "13", - "eventTime": "2022-06-29T00:17:19.862396256Z", - "eventType": "WorkflowExecutionCompleted", - "version": "0", - "taskId": "28312435", - "workflowExecutionCompletedEventAttributes": { - "result": null, - "workflowTaskCompletedEventId": "12", - "newExecutionRunId": "" - } - }, - { - "eventId": "12", - "eventTime": "2022-06-29T00:17:19.862390422Z", - "eventType": "WorkflowTaskCompleted", - "version": "0", - "taskId": "28312434", - "workflowTaskCompletedEventAttributes": { - "scheduledEventId": "10", - "startedEventId": "11", - "identity": "50509@MacBook-Pro-2.lan1@", - "binaryChecksum": "e56c0141e58df0bd405138565d0526f9" - } - }, - { - "eventId": "11", - "eventTime": "2022-06-29T00:17:19.855284381Z", - "eventType": "WorkflowTaskStarted", - "version": "0", - "taskId": "28312431", - "workflowTaskStartedEventAttributes": { - "scheduledEventId": "10", - "identity": "50509@MacBook-Pro-2.lan1@", - "requestId": "ba23ccc5-86f1-46cb-9a6b-a578b2d66ed8" - } - }, - { - "eventId": "10", - "eventTime": "2022-06-29T00:17:19.849841047Z", - "eventType": "WorkflowTaskScheduled", - "version": "0", - "taskId": "28312427", - "workflowTaskScheduledEventAttributes": { - "taskQueue": { - "name": "MacBook-Pro-2.lan1:2be09f20-2ddc-49ec-9d3c-7b57f145f2b6", - "kind": "Sticky" - }, - "startToCloseTimeout": "20s", - "attempt": 1 - } - }, - { - "eventId": "9", - "eventTime": "2022-06-29T00:17:19.849836589Z", - "eventType": "ActivityTaskTimedOut", - "version": "0", - "taskId": "28312426", - "activityTaskTimedOutEventAttributes": { - "failure": { - "message": "activity StartToClose timeout", - "source": "Server", - "stackTrace": "", - "cause": null, - "timeoutFailureInfo": { - "timeoutType": "ScheduleToClose", - "lastHeartbeatDetails": null - } - }, - "scheduledEventId": "7", - "startedEventId": "8", - "retryState": "Timeout" - } - }, - { - "eventId": "8", - "eventTime": "2022-06-29T00:17:18.848894088Z", - "eventType": "ActivityTaskStarted", - "version": "0", - "taskId": "28312425", - "activityTaskStartedEventAttributes": { - "scheduledEventId": "7", - "identity": "50509@MacBook-Pro-2.lan1@", - "requestId": "f8647d1d-3180-43fe-8177-1c0805b12716", - "attempt": 1, - "lastFailure": null - } - }, - { - "eventId": "7", - "eventTime": "2022-06-29T00:17:18.841183297Z", - "eventType": "ActivityTaskScheduled", - "version": "0", - "taskId": "28312357", - "activityTaskScheduledEventAttributes": { - "activityId": "7", - "activityType": { - "name": "activity.timeout" - }, - "taskQueue": { - "name": "canary-task-queue", - "kind": "Normal" - }, - "header": { - "fields": {} - }, - "input": { - "payloads": [ - { - "metadata": { - "encoding": "anNvbi9wbGFpbg==" - }, - "data": "MTY1NjQ2MTgzODgzMDYyMDg4MA==" - } - ] - }, - "scheduleToCloseTimeout": "2s", - "scheduleToStartTimeout": "1s", - "startToCloseTimeout": "1s", - "heartbeatTimeout": "0s", - "workflowTaskCompletedEventId": "4", - "retryPolicy": { - "initialInterval": "1s", - "backoffCoefficient": 2, - "maximumInterval": "100s", - "maximumAttempts": 0, - "nonRetryableErrorTypes": [] - } - } - }, - { - "eventId": "6", - "eventTime": "2022-06-29T00:17:18.841177797Z", - "eventType": "UpsertWorkflowSearchAttributes", - "version": "0", - "taskId": "28312356", - "upsertWorkflowSearchAttributesEventAttributes": { - "workflowTaskCompletedEventId": "4", - "searchAttributes": { - "indexedFields": { - "TemporalChangeVersion": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "WyJpbml0aWFsIHZlcnNpb24tMyJd" - } - } - } - } - }, - { - "eventId": "5", - "eventTime": "2022-06-29T00:17:18.840595463Z", - "eventType": "MarkerRecorded", - "version": "0", - "taskId": "28312355", - "markerRecordedEventAttributes": { - "markerName": "Version", - "details": { - "change-id": { - "payloads": [ - { - "metadata": { - "encoding": "anNvbi9wbGFpbg==" - }, - "data": "ImluaXRpYWwgdmVyc2lvbiI=" - } - ] - }, - "version": { - "payloads": [ - { - "metadata": { - "encoding": "anNvbi9wbGFpbg==" - }, - "data": "Mw==" - } - ] - } - }, - "workflowTaskCompletedEventId": "4", - "header": null, - "failure": null - } - }, - { - "eventId": "4", - "eventTime": "2022-06-29T00:17:18.840578088Z", - "eventType": "WorkflowTaskCompleted", - "version": "0", - "taskId": "28312354", - "workflowTaskCompletedEventAttributes": { - "scheduledEventId": "2", - "startedEventId": "3", - "identity": "50509@MacBook-Pro-2.lan1@", - "binaryChecksum": "e56c0141e58df0bd405138565d0526f9" - } - }, - { - "eventId": "3", - "eventTime": "2022-06-29T00:17:18.830620880Z", - "eventType": "WorkflowTaskStarted", - "version": "0", - "taskId": "28312351", - "workflowTaskStartedEventAttributes": { - "scheduledEventId": "2", - "identity": "50509@MacBook-Pro-2.lan1@", - "requestId": "24863659-6691-44de-a75b-598e812aa99a" - } - }, - { - "eventId": "2", - "eventTime": "2022-06-29T00:17:18.825203213Z", - "eventType": "WorkflowTaskScheduled", - "version": "0", - "taskId": "28312348", - "workflowTaskScheduledEventAttributes": { - "taskQueue": { - "name": "canary-task-queue", - "kind": "Normal" - }, - "startToCloseTimeout": "20s", - "attempt": 1 - } - }, - { - "eventId": "1", - "eventTime": "2022-06-29T00:17:18.815001672Z", - "eventType": "WorkflowExecutionStarted", - "version": "0", - "taskId": "28312344", - "workflowExecutionStartedEventAttributes": { - "workflowType": { - "name": "workflow.timeout" - }, - "parentWorkflowNamespace": "canary", - "parentWorkflowExecution": { - "workflowId": "temporal.canary.cron-workflow.sanity-2022-06-28T20:17:18-04:00", - "runId": "92ce14af-a865-4f3e-8a1e-1eade8ea387a" - }, - "parentInitiatedEventId": "15", - "taskQueue": { - "name": "canary-task-queue", - "kind": "Normal" - }, - "input": { - "payloads": [ - { - "metadata": { - "encoding": "anNvbi9wbGFpbg==" - }, - "data": "MTY1NjQ2MTgzODQxOTQ5MDg4MA==" - }, - { - "metadata": { - "encoding": "anNvbi9wbGFpbg==" - }, - "data": "ImNhbmFyeSI=" - } - ] - }, - "workflowExecutionTimeout": "0s", - "workflowRunTimeout": "1200s", - "workflowTaskTimeout": "20s", - "continuedExecutionRunId": "", - "initiator": "Unspecified", - "continuedFailure": null, - "lastCompletionResult": null, - "originalExecutionRunId": "32a7e384-9b02-447c-a350-5667aba35cc3", - "identity": "", - "firstExecutionRunId": "32a7e384-9b02-447c-a350-5667aba35cc3", - "retryPolicy": null, - "attempt": 1, - "workflowExecutionExpirationTime": null, - "cronSchedule": "", - "firstWorkflowTaskBackoff": "0s", - "memo": null, - "searchAttributes": { - "indexedFields": { - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "ImNoaWxkV29ya2Zsb3dWYWx1ZSI=" - } - } - }, - "prevAutoResetPoints": null, - "header": { - "fields": {} - }, - "parentInitiatedEventVersion": "0" - } - } - ] - }, - "nextPageToken": null -} diff --git a/cypress/fixtures/event-history-completed.json b/cypress/fixtures/event-history-completed.json deleted file mode 100644 index 176adfa3a..000000000 --- a/cypress/fixtures/event-history-completed.json +++ /dev/null @@ -1,275 +0,0 @@ -{ - "history": { - "events": [ - { - "eventId": "1", - "eventTime": "2022-04-28T05:52:26.878394493Z", - "eventType": "WorkflowExecutionStarted", - "version": "0", - "taskId": "1390698", - "workflowExecutionStartedEventAttributes": { - "workflowType": { - "name": "RainbowStatusesWorkflow" - }, - "parentWorkflowNamespace": "", - "parentWorkflowExecution": null, - "parentInitiatedEventId": "0", - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "input": { - "payloads": [ - { - "metadata": { - "encoding": "anNvbi9wbGFpbg==" - }, - "data": "Mg==" - } - ] - }, - "workflowExecutionTimeout": "0s", - "workflowRunTimeout": "0s", - "workflowTaskTimeout": "10s", - "continuedExecutionRunId": "", - "initiator": "Unspecified", - "continuedFailure": null, - "lastCompletionResult": null, - "originalExecutionRunId": "63ef3750-fe4a-427d-848a-9ae30c0165cc", - "identity": "174278@user0@", - "firstExecutionRunId": "63ef3750-fe4a-427d-848a-9ae30c0165cc", - "retryPolicy": null, - "attempt": 1, - "workflowExecutionExpirationTime": null, - "cronSchedule": "", - "firstWorkflowTaskBackoff": "0s", - "memo": null, - "searchAttributes": { - "indexedFields": { - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "ZmFsc2U=" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDQtMjhUMDU6NTI6MjYuODc3NTQ5OTUzWiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "MQ==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "MQ==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtNGRkZmVjIg==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgNGRkZmVjIENvbXBsZXRlZCI=" - } - } - }, - "prevAutoResetPoints": null, - "header": { - "fields": {} - } - } - }, - { - "eventId": "2", - "eventTime": "2022-04-28T05:52:26.878411167Z", - "eventType": "WorkflowTaskScheduled", - "version": "0", - "taskId": "1390699", - "workflowTaskScheduledEventAttributes": { - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "startToCloseTimeout": "10s", - "attempt": 1 - } - }, - { - "eventId": "3", - "eventTime": "2022-04-28T05:52:26.884076144Z", - "eventType": "WorkflowTaskStarted", - "version": "0", - "taskId": "1390703", - "workflowTaskStartedEventAttributes": { - "scheduledEventId": "2", - "identity": "174128@user0@", - "requestId": "6700a890-14f9-4679-9d02-f5e861321bc9" - } - }, - { - "eventId": "4", - "eventTime": "2022-04-28T05:52:26.889067790Z", - "eventType": "WorkflowTaskCompleted", - "version": "0", - "taskId": "1390706", - "workflowTaskCompletedEventAttributes": { - "scheduledEventId": "2", - "startedEventId": "3", - "identity": "174128@user0@", - "binaryChecksum": "abfe7e3fc78675a42c9e544a83eb7edb" - } - }, - { - "eventId": "5", - "eventTime": "2022-04-28T05:52:26.889109533Z", - "eventType": "ActivityTaskScheduled", - "version": "0", - "taskId": "1390707", - "activityTaskScheduledEventAttributes": { - "activityId": "5", - "activityType": { - "name": "CompletedActivity" - }, - "namespace": "", - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "header": { - "fields": {} - }, - "input": null, - "scheduleToCloseTimeout": "0s", - "scheduleToStartTimeout": "0s", - "startToCloseTimeout": "3600s", - "heartbeatTimeout": "0s", - "workflowTaskCompletedEventId": "4", - "retryPolicy": { - "initialInterval": "1s", - "backoffCoefficient": 2, - "maximumInterval": "100s", - "maximumAttempts": 1, - "nonRetryableErrorTypes": [] - } - } - }, - { - "eventId": "6", - "eventTime": "2022-04-28T05:52:26.894480893Z", - "eventType": "ActivityTaskStarted", - "version": "0", - "taskId": "1390712", - "activityTaskStartedEventAttributes": { - "scheduledEventId": "5", - "identity": "174128@user0@", - "requestId": "be47e38b-8ebb-4e59-a1f7-1dbec3730c5b", - "attempt": 1, - "lastFailure": null - } - }, - { - "eventId": "7", - "eventTime": "2022-04-28T05:52:26.898301580Z", - "eventType": "ActivityTaskCompleted", - "version": "0", - "taskId": "1390713", - "activityTaskCompletedEventAttributes": { - "result": { - "payloads": [ - { - "metadata": { - "encoding": "anNvbi9wbGFpbg==" - }, - "data": "ImhleSI=" - } - ] - }, - "scheduledEventId": "5", - "startedEventId": "6", - "identity": "174128@user0@" - } - }, - { - "eventId": "8", - "eventTime": "2022-04-28T05:52:26.898307162Z", - "eventType": "WorkflowTaskScheduled", - "version": "0", - "taskId": "1390714", - "workflowTaskScheduledEventAttributes": { - "taskQueue": { - "name": "user0:20715813-649f-4775-999f-5daba250d1d1", - "kind": "Sticky" - }, - "startToCloseTimeout": "10s", - "attempt": 1 - } - }, - { - "eventId": "9", - "eventTime": "2022-04-28T05:52:26.901843957Z", - "eventType": "WorkflowTaskStarted", - "version": "0", - "taskId": "1390718", - "workflowTaskStartedEventAttributes": { - "scheduledEventId": "8", - "identity": "174128@user0@", - "requestId": "0014a5cf-de0d-4ce8-b46a-e3f22968f800" - } - }, - { - "eventId": "10", - "eventTime": "2022-04-28T05:52:26.906189544Z", - "eventType": "WorkflowTaskCompleted", - "version": "0", - "taskId": "1390721", - "workflowTaskCompletedEventAttributes": { - "scheduledEventId": "8", - "startedEventId": "9", - "identity": "174128@user0@", - "binaryChecksum": "abfe7e3fc78675a42c9e544a83eb7edb" - } - }, - { - "eventId": "11", - "eventTime": "2022-04-28T05:52:26.906219239Z", - "eventType": "WorkflowExecutionCompleted", - "version": "0", - "taskId": "1390722", - "workflowExecutionCompletedEventAttributes": { - "result": { - "payloads": [ - { - "metadata": { - "encoding": "anNvbi9wbGFpbg==" - }, - "data": "ImhleSI=" - } - ] - }, - "workflowTaskCompletedEventId": "10", - "newExecutionRunId": "" - } - } - ] - }, - "rawHistory": [], - "nextPageToken": null, - "archived": false -} diff --git a/cypress/fixtures/event-history-continued-as-new.json b/cypress/fixtures/event-history-continued-as-new.json deleted file mode 100644 index 79247b574..000000000 --- a/cypress/fixtures/event-history-continued-as-new.json +++ /dev/null @@ -1,228 +0,0 @@ -{ - "history": { - "events": [ - { - "eventId": "1", - "eventTime": "2022-04-28T05:30:19.470453129Z", - "eventType": "WorkflowExecutionStarted", - "version": "0", - "taskId": "1370980", - "workflowExecutionStartedEventAttributes": { - "workflowType": { - "name": "RainbowStatusesWorkflow" - }, - "parentWorkflowNamespace": "", - "parentWorkflowExecution": null, - "parentInitiatedEventId": "0", - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "input": { - "payloads": [ - { - "metadata": { - "encoding": "anNvbi9wbGFpbg==" - }, - "data": "Ng==" - } - ] - }, - "workflowExecutionTimeout": "0s", - "workflowRunTimeout": "0s", - "workflowTaskTimeout": "10s", - "continuedExecutionRunId": "", - "initiator": "Unspecified", - "continuedFailure": null, - "lastCompletionResult": null, - "originalExecutionRunId": "305ad678-09e6-4718-b15d-c7314ae27825", - "identity": "168773@user0@", - "firstExecutionRunId": "305ad678-09e6-4718-b15d-c7314ae27825", - "retryPolicy": null, - "attempt": 1, - "workflowExecutionExpirationTime": null, - "cronSchedule": "", - "firstWorkflowTaskBackoff": "0s", - "memo": null, - "searchAttributes": { - "indexedFields": { - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "ZmFsc2U=" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDQtMjhUMDU6MzA6MTkuNDY5Njg0ODc4WiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "NQ==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "NQ==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtZDQ5NGM0Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgZDQ5NGM0IENvbnRpbnVlZEFzTmV3Ig==" - } - } - }, - "prevAutoResetPoints": null, - "header": { - "fields": {} - } - } - }, - { - "eventId": "2", - "eventTime": "2022-04-28T05:30:19.470474461Z", - "eventType": "WorkflowTaskScheduled", - "version": "0", - "taskId": "1370981", - "workflowTaskScheduledEventAttributes": { - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "startToCloseTimeout": "10s", - "attempt": 1 - } - }, - { - "eventId": "3", - "eventTime": "2022-04-28T05:30:19.476338736Z", - "eventType": "WorkflowTaskStarted", - "version": "0", - "taskId": "1370985", - "workflowTaskStartedEventAttributes": { - "scheduledEventId": "2", - "identity": "168631@user0@", - "requestId": "118d8ccb-eed6-4b80-8b9c-0a7815fa03f9" - } - }, - { - "eventId": "4", - "eventTime": "2022-04-28T05:30:19.481165Z", - "eventType": "WorkflowTaskCompleted", - "version": "0", - "taskId": "1370988", - "workflowTaskCompletedEventAttributes": { - "scheduledEventId": "2", - "startedEventId": "3", - "identity": "168631@user0@", - "binaryChecksum": "04f0fb34cfd90d692fa1d506c626a598" - } - }, - { - "eventId": "5", - "eventTime": "2022-04-28T05:30:19.481473481Z", - "eventType": "WorkflowExecutionContinuedAsNew", - "version": "0", - "taskId": "1370989", - "workflowExecutionContinuedAsNewEventAttributes": { - "newExecutionRunId": "750fe68f-7e61-4235-af40-99e82110d87b", - "workflowType": { - "name": "RainbowStatusesWorkflow" - }, - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "input": { - "payloads": [ - { - "metadata": { - "encoding": "anNvbi9wbGFpbg==" - }, - "data": "Mg==" - } - ] - }, - "workflowRunTimeout": "0s", - "workflowTaskTimeout": "10s", - "workflowTaskCompletedEventId": "4", - "backoffStartInterval": null, - "initiator": "Unspecified", - "failure": null, - "lastCompletionResult": null, - "header": { - "fields": {} - }, - "memo": null, - "searchAttributes": { - "indexedFields": { - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "ZmFsc2U=" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDQtMjhUMDU6MzA6MTkuNDY5Njg0ODc4WiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "NQ==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "NQ==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtZDQ5NGM0Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgZDQ5NGM0IENvbnRpbnVlZEFzTmV3Ig==" - } - } - } - } - } - ] - }, - "rawHistory": [], - "nextPageToken": null, - "archived": false -} diff --git a/cypress/fixtures/event-history-empty.json b/cypress/fixtures/event-history-empty.json deleted file mode 100644 index 75b835e67..000000000 --- a/cypress/fixtures/event-history-empty.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "history": { - "events": [] - }, - "rawHistory": [], - "nextPageToken": null, - "archived": false -} diff --git a/cypress/fixtures/event-history-failed.json b/cypress/fixtures/event-history-failed.json deleted file mode 100644 index 20b3c296a..000000000 --- a/cypress/fixtures/event-history-failed.json +++ /dev/null @@ -1,294 +0,0 @@ -{ - "history": { - "events": [ - { - "eventId": "1", - "eventTime": "2022-04-28T05:30:19.445614773Z", - "eventType": "WorkflowExecutionStarted", - "version": "0", - "taskId": "1367526", - "workflowExecutionStartedEventAttributes": { - "workflowType": { - "name": "RainbowStatusesWorkflow" - }, - "parentWorkflowNamespace": "", - "parentWorkflowExecution": null, - "parentInitiatedEventId": "0", - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "input": { - "payloads": [ - { - "metadata": { - "encoding": "anNvbi9wbGFpbg==" - }, - "data": "Mw==" - } - ] - }, - "workflowExecutionTimeout": "0s", - "workflowRunTimeout": "0s", - "workflowTaskTimeout": "10s", - "continuedExecutionRunId": "", - "initiator": "Unspecified", - "continuedFailure": null, - "lastCompletionResult": null, - "originalExecutionRunId": "cd6eac83-95e0-4c3f-8532-4a062b66d65b", - "identity": "168773@user0@", - "firstExecutionRunId": "cd6eac83-95e0-4c3f-8532-4a062b66d65b", - "retryPolicy": null, - "attempt": 1, - "workflowExecutionExpirationTime": null, - "cronSchedule": "", - "firstWorkflowTaskBackoff": "0s", - "memo": null, - "searchAttributes": { - "indexedFields": { - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "dHJ1ZQ==" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDQtMjhUMDU6MzA6MTkuNDQ0ODc1NjU4WiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "Mg==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "Mg==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtZDQ5NGM0Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgZDQ5NGM0IEZhaWxlZCI=" - } - } - }, - "prevAutoResetPoints": null, - "header": { - "fields": {} - } - } - }, - { - "eventId": "2", - "eventTime": "2022-04-28T05:30:19.445630017Z", - "eventType": "WorkflowTaskScheduled", - "version": "0", - "taskId": "1367527", - "workflowTaskScheduledEventAttributes": { - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "startToCloseTimeout": "10s", - "attempt": 1 - } - }, - { - "eventId": "3", - "eventTime": "2022-04-28T05:30:19.455830647Z", - "eventType": "WorkflowTaskStarted", - "version": "0", - "taskId": "1367537", - "workflowTaskStartedEventAttributes": { - "scheduledEventId": "2", - "identity": "168631@user0@", - "requestId": "9c328226-6823-4ce9-9ff8-e4be34b11336" - } - }, - { - "eventId": "4", - "eventTime": "2022-04-28T05:30:19.459978643Z", - "eventType": "WorkflowTaskCompleted", - "version": "0", - "taskId": "1367540", - "workflowTaskCompletedEventAttributes": { - "scheduledEventId": "2", - "startedEventId": "3", - "identity": "168631@user0@", - "binaryChecksum": "04f0fb34cfd90d692fa1d506c626a598" - } - }, - { - "eventId": "5", - "eventTime": "2022-04-28T05:30:19.460006335Z", - "eventType": "ActivityTaskScheduled", - "version": "0", - "taskId": "1367541", - "activityTaskScheduledEventAttributes": { - "activityId": "5", - "activityType": { - "name": "FailedActivity" - }, - "namespace": "", - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "header": { - "fields": {} - }, - "input": null, - "scheduleToCloseTimeout": "0s", - "scheduleToStartTimeout": "0s", - "startToCloseTimeout": "3600s", - "heartbeatTimeout": "0s", - "workflowTaskCompletedEventId": "4", - "retryPolicy": { - "initialInterval": "1s", - "backoffCoefficient": 2, - "maximumInterval": "100s", - "maximumAttempts": 1, - "nonRetryableErrorTypes": [] - } - } - }, - { - "eventId": "6", - "eventTime": "2022-04-28T05:30:19.464253422Z", - "eventType": "ActivityTaskStarted", - "version": "0", - "taskId": "1367546", - "activityTaskStartedEventAttributes": { - "scheduledEventId": "5", - "identity": "168631@user0@", - "requestId": "5259a8ac-da1d-46ed-8d10-22fb4df32e41", - "attempt": 1, - "lastFailure": null - } - }, - { - "eventId": "7", - "eventTime": "2022-04-28T05:30:19.467461618Z", - "eventType": "ActivityTaskFailed", - "version": "0", - "taskId": "1367547", - "activityTaskFailedEventAttributes": { - "failure": { - "message": "manual failure", - "source": "GoSDK", - "stackTrace": "", - "cause": null, - "applicationFailureInfo": { - "type": "", - "nonRetryable": false, - "details": null - } - }, - "scheduledEventId": "5", - "startedEventId": "6", - "identity": "168631@user0@", - "retryState": "MaximumAttemptsReached" - } - }, - { - "eventId": "8", - "eventTime": "2022-04-28T05:30:19.467466352Z", - "eventType": "WorkflowTaskScheduled", - "version": "0", - "taskId": "1367548", - "workflowTaskScheduledEventAttributes": { - "taskQueue": { - "name": "user0:d433b889-ba64-4226-ba2c-8e6a07e19d63", - "kind": "Sticky" - }, - "startToCloseTimeout": "10s", - "attempt": 1 - } - }, - { - "eventId": "9", - "eventTime": "2022-04-28T05:30:19.471343817Z", - "eventType": "WorkflowTaskStarted", - "version": "0", - "taskId": "1367552", - "workflowTaskStartedEventAttributes": { - "scheduledEventId": "8", - "identity": "168631@user0@", - "requestId": "6b7e2000-e452-43c0-9048-056255d32034" - } - }, - { - "eventId": "10", - "eventTime": "2022-04-28T05:30:19.475521623Z", - "eventType": "WorkflowTaskCompleted", - "version": "0", - "taskId": "1367555", - "workflowTaskCompletedEventAttributes": { - "scheduledEventId": "8", - "startedEventId": "9", - "identity": "168631@user0@", - "binaryChecksum": "04f0fb34cfd90d692fa1d506c626a598" - } - }, - { - "eventId": "11", - "eventTime": "2022-04-28T05:30:19.475534318Z", - "eventType": "WorkflowExecutionFailed", - "version": "0", - "taskId": "1367556", - "workflowExecutionFailedEventAttributes": { - "failure": { - "message": "activity error", - "source": "GoSDK", - "stackTrace": "", - "cause": { - "message": "manual failure", - "source": "GoSDK", - "stackTrace": "", - "cause": null, - "applicationFailureInfo": { - "type": "", - "nonRetryable": false, - "details": null - } - }, - "activityFailureInfo": { - "scheduledEventId": "5", - "startedEventId": "6", - "identity": "168631@user0@", - "activityType": { - "name": "FailedActivity" - }, - "activityId": "5", - "retryState": "MaximumAttemptsReached" - } - }, - "retryState": "RetryPolicyNotSet", - "workflowTaskCompletedEventId": "10", - "newExecutionRunId": "" - } - } - ] - }, - "rawHistory": [], - "nextPageToken": null, - "archived": false -} diff --git a/cypress/fixtures/event-history-running.json b/cypress/fixtures/event-history-running.json deleted file mode 100644 index a819369fd..000000000 --- a/cypress/fixtures/event-history-running.json +++ /dev/null @@ -1,202 +0,0 @@ -{ - "history": { - "events": [ - { - "eventId": "1", - "eventTime": "2022-04-28T05:30:19.427247101Z", - "eventType": "WorkflowExecutionStarted", - "version": "0", - "taskId": "1367516", - "workflowExecutionStartedEventAttributes": { - "workflowType": { - "name": "RainbowStatusesWorkflow" - }, - "parentWorkflowNamespace": "", - "parentWorkflowExecution": null, - "parentInitiatedEventId": "0", - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "input": { - "payloads": [ - { - "metadata": { - "encoding": "anNvbi9wbGFpbg==" - }, - "data": "MQ==" - } - ] - }, - "workflowExecutionTimeout": "0s", - "workflowRunTimeout": "0s", - "workflowTaskTimeout": "10s", - "continuedExecutionRunId": "", - "initiator": "Unspecified", - "continuedFailure": null, - "lastCompletionResult": null, - "originalExecutionRunId": "be1ded4f-3147-4ec7-8efb-30a447fef129", - "identity": "168773@user0@", - "firstExecutionRunId": "be1ded4f-3147-4ec7-8efb-30a447fef129", - "retryPolicy": null, - "attempt": 1, - "workflowExecutionExpirationTime": null, - "cronSchedule": "", - "firstWorkflowTaskBackoff": "0s", - "memo": null, - "searchAttributes": { - "indexedFields": { - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "dHJ1ZQ==" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDQtMjhUMDU6MzA6MTkuNDI2MTkyMDg4WiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "MA==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "MA==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtZDQ5NGM0Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgZDQ5NGM0IFJ1bm5pbmci" - } - } - }, - "prevAutoResetPoints": null, - "header": { - "fields": {} - } - } - }, - { - "eventId": "2", - "eventTime": "2022-04-28T05:30:19.427264889Z", - "eventType": "WorkflowTaskScheduled", - "version": "0", - "taskId": "1367517", - "workflowTaskScheduledEventAttributes": { - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "startToCloseTimeout": "10s", - "attempt": 1 - } - }, - { - "eventId": "3", - "eventTime": "2022-04-28T05:30:19.436957880Z", - "eventType": "WorkflowExecutionSignaled", - "version": "0", - "taskId": "1367521", - "workflowExecutionSignaledEventAttributes": { - "signalName": "customSignal", - "input": { - "payloads": [ - { - "metadata": { - "encoding": "anNvbi9wbGFpbg==" - }, - "data": "eyJIZXkiOiJmcm9tIE1hcnMiLCJBdCI6IjIwMjItMDQtMjhUMDE6MzA6MTkuNDM0MjYyMzM1LTA0OjAwIn0=" - } - ] - }, - "identity": "168773@user0@", - "header": { - "fields": {} - } - } - }, - { - "eventId": "4", - "eventTime": "2022-04-28T05:30:19.440848942Z", - "eventType": "WorkflowTaskStarted", - "version": "0", - "taskId": "1367523", - "workflowTaskStartedEventAttributes": { - "scheduledEventId": "2", - "identity": "168631@user0@", - "requestId": "c2a89d60-2c1b-4a8f-b312-f11385722e7e" - } - }, - { - "eventId": "5", - "eventTime": "2022-04-28T05:30:19.445592990Z", - "eventType": "WorkflowTaskCompleted", - "version": "0", - "taskId": "1367529", - "workflowTaskCompletedEventAttributes": { - "scheduledEventId": "2", - "startedEventId": "4", - "identity": "168631@user0@", - "binaryChecksum": "04f0fb34cfd90d692fa1d506c626a598" - } - }, - { - "eventId": "6", - "eventTime": "2022-04-28T05:30:19.445631589Z", - "eventType": "ActivityTaskScheduled", - "version": "0", - "taskId": "1367530", - "activityTaskScheduledEventAttributes": { - "activityId": "6", - "activityType": { - "name": "LongActivity" - }, - "namespace": "", - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "header": { - "fields": {} - }, - "input": null, - "scheduleToCloseTimeout": "0s", - "scheduleToStartTimeout": "0s", - "startToCloseTimeout": "3600s", - "heartbeatTimeout": "0s", - "workflowTaskCompletedEventId": "5", - "retryPolicy": { - "initialInterval": "1s", - "backoffCoefficient": 2, - "maximumInterval": "100s", - "maximumAttempts": 1, - "nonRetryableErrorTypes": [] - } - } - } - ] - }, - "rawHistory": [], - "nextPageToken": null, - "archived": false -} diff --git a/cypress/fixtures/event-history-timed-out.json b/cypress/fixtures/event-history-timed-out.json deleted file mode 100644 index 72ab036ac..000000000 --- a/cypress/fixtures/event-history-timed-out.json +++ /dev/null @@ -1,189 +0,0 @@ -{ - "history": { - "events": [ - { - "eventId": "1", - "eventTime": "2022-04-28T05:30:19.474604962Z", - "eventType": "WorkflowExecutionStarted", - "version": "0", - "taskId": "1433744", - "workflowExecutionStartedEventAttributes": { - "workflowType": { - "name": "RainbowStatusesWorkflow" - }, - "parentWorkflowNamespace": "", - "parentWorkflowExecution": null, - "parentInitiatedEventId": "0", - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "input": { - "payloads": [ - { - "metadata": { - "encoding": "anNvbi9wbGFpbg==" - }, - "data": "Nw==" - } - ] - }, - "workflowExecutionTimeout": "1s", - "workflowRunTimeout": "1s", - "workflowTaskTimeout": "1s", - "continuedExecutionRunId": "", - "initiator": "Unspecified", - "continuedFailure": null, - "lastCompletionResult": null, - "originalExecutionRunId": "ea93ef88-b551-4627-9004-5196b64307e5", - "identity": "168773@user0@", - "firstExecutionRunId": "ea93ef88-b551-4627-9004-5196b64307e5", - "retryPolicy": null, - "attempt": 1, - "workflowExecutionExpirationTime": "2022-04-28T05:30:20.474Z", - "cronSchedule": "", - "firstWorkflowTaskBackoff": "0s", - "memo": null, - "searchAttributes": { - "indexedFields": { - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "dHJ1ZQ==" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDQtMjhUMDU6MzA6MTkuNDczOTIzNDM4WiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "Ng==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "Ng==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtZDQ5NGM0Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgZDQ5NGM0IFRpbWVkT3V0Ig==" - } - } - }, - "prevAutoResetPoints": null, - "header": { - "fields": {} - } - } - }, - { - "eventId": "2", - "eventTime": "2022-04-28T05:30:19.474619403Z", - "eventType": "WorkflowTaskScheduled", - "version": "0", - "taskId": "1433745", - "workflowTaskScheduledEventAttributes": { - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "startToCloseTimeout": "1s", - "attempt": 1 - } - }, - { - "eventId": "3", - "eventTime": "2022-04-28T05:30:19.482733116Z", - "eventType": "WorkflowTaskStarted", - "version": "0", - "taskId": "1433750", - "workflowTaskStartedEventAttributes": { - "scheduledEventId": "2", - "identity": "168631@user0@", - "requestId": "6a82f569-41d1-4c23-bb0f-66b7c9dffcc3" - } - }, - { - "eventId": "4", - "eventTime": "2022-04-28T05:30:19.487213526Z", - "eventType": "WorkflowTaskCompleted", - "version": "0", - "taskId": "1433753", - "workflowTaskCompletedEventAttributes": { - "scheduledEventId": "2", - "startedEventId": "3", - "identity": "168631@user0@", - "binaryChecksum": "04f0fb34cfd90d692fa1d506c626a598" - } - }, - { - "eventId": "5", - "eventTime": "2022-04-28T05:30:19.487240069Z", - "eventType": "ActivityTaskScheduled", - "version": "0", - "taskId": "1433754", - "activityTaskScheduledEventAttributes": { - "activityId": "5", - "activityType": { - "name": "LongActivity" - }, - "namespace": "", - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "header": { - "fields": {} - }, - "input": null, - "scheduleToCloseTimeout": "1s", - "scheduleToStartTimeout": "1s", - "startToCloseTimeout": "1s", - "heartbeatTimeout": "0s", - "workflowTaskCompletedEventId": "4", - "retryPolicy": { - "initialInterval": "1s", - "backoffCoefficient": 2, - "maximumInterval": "100s", - "maximumAttempts": 1, - "nonRetryableErrorTypes": [] - } - } - }, - { - "eventId": "6", - "eventTime": "2022-04-28T05:30:20.475213345Z", - "eventType": "WorkflowExecutionTimedOut", - "version": "0", - "taskId": "1433759", - "workflowExecutionTimedOutEventAttributes": { - "retryState": "Timeout", - "newExecutionRunId": "" - } - } - ] - }, - "rawHistory": [], - "nextPageToken": null, - "archived": false -} diff --git a/cypress/fixtures/event-history-with-no-activities.json b/cypress/fixtures/event-history-with-no-activities.json deleted file mode 100644 index 528d67bce..000000000 --- a/cypress/fixtures/event-history-with-no-activities.json +++ /dev/null @@ -1,196 +0,0 @@ -{ - "history": { - "events": [ - { - "eventId": "1", - "eventTime": "2022-04-11T12:59:24.522874755Z", - "eventType": "WorkflowExecutionStarted", - "version": "0", - "taskId": "1048675", - "workflowExecutionStartedEventAttributes": { - "workflowType": { - "name": "RainbowStatusesWorkflow" - }, - "parentWorkflowNamespace": "", - "parentWorkflowExecution": null, - "parentInitiatedEventId": "0", - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "input": { - "payloads": [ - { - "metadata": { - "encoding": "anNvbi9wbGFpbg==" - }, - "data": "Mg==" - } - ] - }, - "workflowExecutionTimeout": "0s", - "workflowRunTimeout": "0s", - "workflowTaskTimeout": "10s", - "continuedExecutionRunId": "", - "initiator": "Unspecified", - "continuedFailure": null, - "lastCompletionResult": null, - "originalExecutionRunId": "db7b0929-24bc-424c-a935-a1f8da69755e", - "identity": "57997@MacBook-Pro@", - "firstExecutionRunId": "db7b0929-24bc-424c-a935-a1f8da69755e", - "retryPolicy": null, - "attempt": 1, - "workflowExecutionExpirationTime": null, - "cronSchedule": "", - "firstWorkflowTaskBackoff": "0s", - "memo": null, - "searchAttributes": { - "indexedFields": { - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "ZmFsc2U=" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDQtMTFUMTI6NTk6MjQuNTIzMzA5WiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "MQ==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "MQ==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtYjEyNDUzIg==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgYjEyNDUzIENvbXBsZXRlZCI=" - } - } - }, - "prevAutoResetPoints": null, - "header": { - "fields": {} - } - } - }, - { - "eventId": "2", - "eventTime": "2022-04-11T12:59:24.522889797Z", - "eventType": "WorkflowTaskScheduled", - "version": "0", - "taskId": "1048676", - "workflowTaskScheduledEventAttributes": { - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "startToCloseTimeout": "10s", - "attempt": 1 - } - }, - { - "eventId": "3", - "eventTime": "2022-04-11T12:59:24.544864255Z", - "eventType": "WorkflowTaskStarted", - "version": "0", - "taskId": "1048690", - "workflowTaskStartedEventAttributes": { - "scheduledEventId": "2", - "identity": "57979@MacBook-Pro@", - "requestId": "1e90c204-0535-462c-b335-4052d3658e02" - } - }, - { - "eventId": "4", - "eventTime": "2022-04-11T12:59:24.554243505Z", - "eventType": "WorkflowTaskCompleted", - "version": "0", - "taskId": "1048702", - "workflowTaskCompletedEventAttributes": { - "scheduledEventId": "2", - "startedEventId": "3", - "identity": "57979@MacBook-Pro@", - "binaryChecksum": "32e04259e3a1e137fa66893bb617979a" - } - }, - { - "eventId": "8", - "eventTime": "2022-04-11T12:59:24.596873172Z", - "eventType": "WorkflowTaskScheduled", - "version": "0", - "taskId": "1048731", - "workflowTaskScheduledEventAttributes": { - "taskQueue": { - "name": "MacBook-Pro:2ed93de6-ee9e-4d83-bde1-9d08303b992c", - "kind": "Sticky" - }, - "startToCloseTimeout": "10s", - "attempt": 1 - } - }, - { - "eventId": "9", - "eventTime": "2022-04-11T12:59:24.607690588Z", - "eventType": "WorkflowTaskStarted", - "version": "0", - "taskId": "1048741", - "workflowTaskStartedEventAttributes": { - "scheduledEventId": "8", - "identity": "57979@MacBook-Pro@", - "requestId": "9c6532d7-a87c-4f3b-ac87-b91748b97906" - } - }, - { - "eventId": "10", - "eventTime": "2022-04-11T12:59:24.614579172Z", - "eventType": "WorkflowTaskCompleted", - "version": "0", - "taskId": "1048744", - "workflowTaskCompletedEventAttributes": { - "scheduledEventId": "8", - "startedEventId": "9", - "identity": "57979@MacBook-Pro@", - "binaryChecksum": "32e04259e3a1e137fa66893bb617979a" - } - }, - { - "eventId": "11", - "eventTime": "2022-04-11T12:59:24.614591797Z", - "eventType": "WorkflowExecutionCompleted", - "version": "0", - "taskId": "1048745", - "workflowExecutionCompletedEventAttributes": { - "result": null, - "workflowTaskCompletedEventId": "10", - "newExecutionRunId": "" - } - } - ] - }, - "rawHistory": [], - "nextPageToken": null, - "archived": false -} diff --git a/cypress/fixtures/github-releases.json b/cypress/fixtures/github-releases.json deleted file mode 100644 index 3dc50e843..000000000 --- a/cypress/fixtures/github-releases.json +++ /dev/null @@ -1,80 +0,0 @@ -[ - { - "url": "https://api.github.com/repos/temporalio/ui-server/releases/65400787", - "assets_url": "https://api.github.com/repos/temporalio/ui-server/releases/65400787/assets", - "upload_url": "https://uploads.github.com/repos/temporalio/ui-server/releases/65400787/assets{?name,label}", - "html_url": "https://github.com/temporalio/ui-server/releases/tag/v2.1.0", - "id": 65400787, - "author": { - "login": "Alex-Tideman", - "id": 7967403, - "node_id": "MDQ6VXNlcjc5Njc0MDM=", - "avatar_url": "https://avatars.githubusercontent.com/u/7967403?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/Alex-Tideman", - "html_url": "https://github.com/Alex-Tideman", - "followers_url": "https://api.github.com/users/Alex-Tideman/followers", - "following_url": "https://api.github.com/users/Alex-Tideman/following{/other_user}", - "gists_url": "https://api.github.com/users/Alex-Tideman/gists{/gist_id}", - "starred_url": "https://api.github.com/users/Alex-Tideman/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/Alex-Tideman/subscriptions", - "organizations_url": "https://api.github.com/users/Alex-Tideman/orgs", - "repos_url": "https://api.github.com/users/Alex-Tideman/repos", - "events_url": "https://api.github.com/users/Alex-Tideman/events{/privacy}", - "received_events_url": "https://api.github.com/users/Alex-Tideman/received_events", - "type": "User", - "site_admin": false - }, - "node_id": "RE_kwDOE-funs4D5e_T", - "tag_name": "v2.1.0", - "target_commitish": "main", - "name": "v2.1.0", - "draft": false, - "prerelease": false, - "created_at": "2022-04-26T19:41:56Z", - "published_at": "2022-04-26T19:54:16Z", - "assets": [], - "tarball_url": "https://api.github.com/repos/temporalio/ui-server/tarball/v2.1.0", - "zipball_url": "https://api.github.com/repos/temporalio/ui-server/zipball/v2.1.0", - "body": "* 2022-04-26 - f773de7 - Put workers in the same table; respect time settings (#490)\r\n* 2022-04-26 - e7fc491 - Add reverse query param to event history route (#489)\r\n* 2022-04-26 - 2b40da6 - Refactoring: Event Groups and Event Types (#488)\r\n* 2022-04-26 - 77e1edd - Use native underlines for links (#484)\r\n* 2022-04-26 - 5284e51 - Flip input and label for expand all (#487)\r\n* 2022-04-26 - 8ad598d - Fix archived workflows query and to only show with both archived and visibility enabled (#486)\r\n* 2022-04-26 - ed12424 - Loading state for Results when the workflow is running (#483)\r\n* 2022-04-26 - bc9d4c7 - Address lint issues (#482)\r\n* 2022-04-25 - 1a58b71 - Make banner component reusable (#469)\r\n* 2022-04-25 - 2477c06 - Add debouncing to workflow filters (#481)\r\n* 2022-04-25 - 9f5f249 - Make the whole feedback button a link (#473)\r\n* 2022-04-25 - da43f4d - Remove summary/full view and add feed view with expand all toggle (#478)\r\n* 2022-04-25 - 12a2f25 - Address bugs on workflow execution page (#480)\r\n* 2022-04-25 - bf854e3 - Remove headers from __error (#479)\r\n* 2022-04-25 - 2525b62 - Redirect to login if unauthorized (#477)" - }, - { - "url": "https://api.github.com/repos/temporalio/ui-server/releases/65091326", - "assets_url": "https://api.github.com/repos/temporalio/ui-server/releases/65091326/assets", - "upload_url": "https://uploads.github.com/repos/temporalio/ui-server/releases/65091326/assets{?name,label}", - "html_url": "https://github.com/temporalio/ui-server/releases/tag/v2.0.4", - "id": 65091326, - "author": { - "login": "Alex-Tideman", - "id": 7967403, - "node_id": "MDQ6VXNlcjc5Njc0MDM=", - "avatar_url": "https://avatars.githubusercontent.com/u/7967403?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/Alex-Tideman", - "html_url": "https://github.com/Alex-Tideman", - "followers_url": "https://api.github.com/users/Alex-Tideman/followers", - "following_url": "https://api.github.com/users/Alex-Tideman/following{/other_user}", - "gists_url": "https://api.github.com/users/Alex-Tideman/gists{/gist_id}", - "starred_url": "https://api.github.com/users/Alex-Tideman/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/Alex-Tideman/subscriptions", - "organizations_url": "https://api.github.com/users/Alex-Tideman/orgs", - "repos_url": "https://api.github.com/users/Alex-Tideman/repos", - "events_url": "https://api.github.com/users/Alex-Tideman/events{/privacy}", - "received_events_url": "https://api.github.com/users/Alex-Tideman/received_events", - "type": "User", - "site_admin": false - }, - "node_id": "RE_kwDOE-funs4D4Tb-", - "tag_name": "v2.0.4", - "target_commitish": "main", - "name": "v2.0.4", - "draft": false, - "prerelease": false, - "created_at": "2022-04-22T15:13:01Z", - "published_at": "2022-04-22T15:35:33Z", - "assets": [], - "tarball_url": "https://api.github.com/repos/temporalio/ui-server/tarball/v2.0.4", - "zipball_url": "https://api.github.com/repos/temporalio/ui-server/zipball/v2.0.4", - "body": "2022-04-22 - ea08294 - Update link colors (#472)\r\n2022-04-22 - fa50965 - Use props for export history url fetch, not params (#471)\r\n" - } -] diff --git a/cypress/fixtures/namespace.json b/cypress/fixtures/namespace.json deleted file mode 100644 index 22be99417..000000000 --- a/cypress/fixtures/namespace.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "namespaceInfo": { - "name": "default", - "state": "Registered", - "description": "Default namespace for Temporal Server.", - "ownerEmail": "", - "data": {}, - "id": "398cb3a0-112f-4a36-991c-766dd8001649" - }, - "config": { - "workflowExecutionRetentionTtl": "86400s", - "badBinaries": { - "binaries": {} - }, - "historyArchivalState": "Disabled", - "historyArchivalUri": "", - "visibilityArchivalState": "Disabled", - "visibilityArchivalUri": "" - }, - "replicationConfig": { - "activeClusterName": "us-east1", - "clusters": [ - { - "clusterName": "us-east1" - }, - { - "clusterName": "us-east2" - } - ], - "state": "Unspecified" - }, - "failoverVersion": "0", - "isGlobalNamespace": false -} diff --git a/cypress/fixtures/namespaces.json b/cypress/fixtures/namespaces.json deleted file mode 100644 index e1d09a08a..000000000 --- a/cypress/fixtures/namespaces.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - "namespaces": [ - { - "namespaceInfo": { - "name": "temporal-system", - "state": "Registered", - "description": "Temporal internal system namespace", - "ownerEmail": "temporal-core@temporal.io", - "data": {}, - "id": "32049b68-7872-4094-8e63-d0dd59896a83" - }, - "config": { - "workflowExecutionRetentionTtl": "604800s", - "badBinaries": { - "binaries": {} - }, - "historyArchivalState": "Disabled", - "historyArchivalUri": "", - "visibilityArchivalState": "Disabled", - "visibilityArchivalUri": "" - }, - "replicationConfig": { - "activeClusterName": "active", - "clusters": [ - { - "clusterName": "active" - } - ], - "state": "Unspecified" - }, - "failoverVersion": "0", - "isGlobalNamespace": false - }, - { - "namespaceInfo": { - "name": "default", - "state": "Registered", - "description": "Default namespace for Temporal Server.", - "ownerEmail": "", - "data": {}, - "id": "398cb3a0-112f-4a36-991c-766dd8001649" - }, - "config": { - "workflowExecutionRetentionTtl": "86400s", - "badBinaries": { - "binaries": {} - }, - "historyArchivalState": "Disabled", - "historyArchivalUri": "", - "visibilityArchivalState": "Disabled", - "visibilityArchivalUri": "" - }, - "replicationConfig": { - "activeClusterName": "us-east1", - "clusters": [ - { - "clusterName": "us-east1" - }, - { - "clusterName": "us-east2" - } - ], - "state": "Unspecified" - }, - "failoverVersion": "0", - "isGlobalNamespace": false - }, - { - "namespaceInfo": { - "name": "some-other-namespace", - "state": "Registered", - "description": "", - "ownerEmail": "", - "data": {}, - "id": "5411056f-9bd0-4b4e-90fa-e88e3031a0d0" - }, - "config": { - "workflowExecutionRetentionTtl": "259200s", - "badBinaries": { - "binaries": {} - }, - "historyArchivalState": "Enabled", - "historyArchivalUri": "", - "visibilityArchivalState": "Enabled", - "visibilityArchivalUri": "" - }, - "replicationConfig": { - "activeClusterName": "active", - "clusters": [ - { - "clusterName": "active" - } - ], - "state": "Unspecified" - }, - "failoverVersion": "0", - "isGlobalNamespace": false - } - ], - "nextPageToken": null -} diff --git a/cypress/fixtures/query-java-error-unknown-type.json b/cypress/fixtures/query-java-error-unknown-type.json deleted file mode 100644 index f49a5269c..000000000 --- a/cypress/fixtures/query-java-error-unknown-type.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "code": 3, - "message": "java.lang.IllegalArgumentException: Unknown query type: @@temporal-internal__list, knownTypes=[query1, query2]\n\tat io.temporal.internal.sync.QueryDispatcher.handleQuery(QueryDispatcher.java:79)\n\tat io.temporal.internal.sync.SyncWorkflowContext.handleQuery(SyncWorkflowContext.java:277)\n\tat io.temporal.internal.sync.WorkflowExecuteRunnable.handleQuery(WorkflowExecuteRunnable.java:118)\n\tat io.temporal.internal.sync.SyncWorkflow.query(SyncWorkflow.java:187)\n\tat io.temporal.internal.replay.ReplayWorkflowExecutor.query(ReplayWorkflowExecutor.java:136)\n\tat io.temporal.internal.replay.ReplayWorkflowRunTaskHandler.handleQueryWorkflowTask(ReplayWorkflowRunTaskHandler.java:241)\n\tat io.temporal.internal.replay.ReplayWorkflowTaskHandler.handleWorkflowTaskWithQuery(ReplayWorkflowTaskHandler.java:117)\n\tat io.temporal.internal.replay.ReplayWorkflowTaskHandler.handleWorkflowTask(ReplayWorkflowTaskHandler.java:97)\n\tat io.temporal.internal.worker.WorkflowWorker$TaskHandlerImpl.handleTask(WorkflowWorker.java:277)\n\tat io.temporal.internal.worker.WorkflowWorker$TaskHandlerImpl.handle(WorkflowWorker.java:231)\n\tat io.temporal.internal.worker.WorkflowWorker$TaskHandlerImpl.handle(WorkflowWorker.java:173)\n\tat io.temporal.internal.worker.PollTaskExecutor.lambda$process$0(PollTaskExecutor.java:93)\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n\tat java.lang.Thread.run(Thread.java:750)\n", - "details": [ - { - "typeUrl": "type.googleapis.com/temporal.api.errordetails.v1.QueryFailedFailure", - "value": null - } - ] -} diff --git a/cypress/fixtures/query-stack-trace-error.json b/cypress/fixtures/query-stack-trace-error.json deleted file mode 100644 index 0202af581..000000000 --- a/cypress/fixtures/query-stack-trace-error.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "queryResult": { - "payloads": [ - { - "an": "error" - } - ] - }, - "queryRejected": null -} diff --git a/cypress/fixtures/query-stack-trace.json b/cypress/fixtures/query-stack-trace.json deleted file mode 100644 index b1be8b47e..000000000 --- a/cypress/fixtures/query-stack-trace.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "queryResult": { - "payloads": [ - { - "metadata": { - "encoding": "anNvbi9wbGFpbg==" - }, - "data": "ImNvcm91dGluZSByb290IFtibG9ja2VkIG9uIGNoYW4tMS5SZWNlaXZlXTpcbmdvLnRlbXBvcmFsLmlvL3Nkay9pbnRlcm5hbC4oKmRlY29kZUZ1dHVyZUltcGwpLkdldCgweGMwMDAxMjc0YjgsIHsweGUwZmNmMCwgMHhjMDAwMmViNDEwfSwgezB4MD8sIDB4MH0pXG5cdC9ob21lL3VzZXIwL2dvL3BrZy9tb2QvZ28udGVtcG9yYWwuaW8vc2RrQHYxLjE0LjAvaW50ZXJuYWwvaW50ZXJuYWxfd29ya2Zsb3cuZ286MTMzNCArMHg0Y1xuZ2l0aHViLmNvbS90ZW1wb3JhbGlvL3NhbXBsZXMtZ28vdGVtcG9yYWwtZml4dHVyZXMvcmFpbmJvdy1zdGF0dXNlcy5SYWluYm93U3RhdHVzZXNXb3JrZmxvdyh7MHhlMGZjZjAsIDB4YzAwMDJlYjMyMH0sIDB4MSlcblx0L2hvbWUvdXNlcjAvc2FtcGxlcy1nby90ZW1wb3JhbC1maXh0dXJlcy9yYWluYm93LXN0YXR1c2VzL3dvcmtmbG93LmdvOjMwICsweDJkMFxucmVmbGVjdC5WYWx1ZS5jYWxsKHsweGJmOWYyMD8sIDB4ZDQ5ZWY4PywgMHg3ZmI1NjlmMGU1Yjg/fSwgezB4Y2U3NzgwLCAweDR9LCB7MHhjMDAwMmViMzUwLCAweDIsIDB4MjAzMDAwP30pXG5cdC91c3IvbGliL2dvL3NyYy9yZWZsZWN0L3ZhbHVlLmdvOjU1NiArMHg4NDVcbnJlZmxlY3QuVmFsdWUuQ2FsbCh7MHhiZjlmMjA/LCAweGQ0OWVmOD8sIDB4NDA0YzRjP30sIHsweGMwMDAyZWIzNTAsIDB4MiwgMHgyfSlcblx0L3Vzci9saWIvZ28vc3JjL3JlZmxlY3QvdmFsdWUuZ286MzM5ICsweGJmXG5nby50ZW1wb3JhbC5pby9zZGsvaW50ZXJuYWwuZXhlY3V0ZUZ1bmN0aW9uKHsweGJmOWYyMCwgMHhkNDllZjh9LCB7MHhjMDAwMTczMWEwLCAweDIsIDB4YzAwMDEwMDAwMD99KVxuXHQvaG9tZS91c2VyMC9nby9wa2cvbW9kL2dvLnRlbXBvcmFsLmlvL3Nka0B2MS4xNC4wL2ludGVybmFsL2ludGVybmFsX3dvcmtlci5nbzoxNTUzICsweDEzNlxuZ28udGVtcG9yYWwuaW8vc2RrL2ludGVybmFsLigqd29ya2Zsb3dFbnZpcm9ubWVudEludGVyY2VwdG9yKS5FeGVjdXRlV29ya2Zsb3coMHhjMDAwMmY2MjMwLCB7MHhlMGZiYTA/LCAweGMwMDAxMzFkYTB9LCAweGMwMDAxMjc0YTApXG5cdC9ob21lL3VzZXIwL2dvL3BrZy9tb2QvZ28udGVtcG9yYWwuaW8vc2RrQHYxLjE0LjAvaW50ZXJuYWwvd29ya2Zsb3cuZ286NDE5ICsweDE2NlxuZ28udGVtcG9yYWwuaW8vc2RrL2ludGVybmFsLigqd29ya2Zsb3dFeGVjdXRvcikuRXhlY3V0ZSgweGMwMDAxNmJjODAsIHsweGUwZmJhMCwgMHhjMDAwMTMxZGEwfSwgMHgyNT8pXG5cdC9ob21lL3VzZXIwL2dvL3BrZy9tb2QvZ28udGVtcG9yYWwuaW8vc2RrQHYxLjE0LjAvaW50ZXJuYWwvaW50ZXJuYWxfd29ya2VyLmdvOjc1MyArMHgyOTJcbmdvLnRlbXBvcmFsLmlvL3Nkay9pbnRlcm5hbC4oKnN5bmNXb3JrZmxvd0RlZmluaXRpb24pLkV4ZWN1dGUuZnVuYzEoezB4ZTBmY2YwLCAweGMwMDAyZWIxNzB9KVxuXHQvaG9tZS91c2VyMC9nby9wa2cvbW9kL2dvLnRlbXBvcmFsLmlvL3Nka0B2MS4xNC4wL2ludGVybmFsL2ludGVybmFsX3dvcmtmbG93LmdvOjUwMCArMHhjZCI=" - } - ] - }, - "queryRejected": null -} diff --git a/cypress/fixtures/query.json b/cypress/fixtures/query.json deleted file mode 100644 index f4f39227a..000000000 --- a/cypress/fixtures/query.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "queryRejected": null, - "queryResult": { - "payloads": [] - } -} diff --git a/cypress/fixtures/schedule.json b/cypress/fixtures/schedule.json deleted file mode 100644 index 5dde3293d..000000000 --- a/cypress/fixtures/schedule.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "schedule": { - "spec": { - "calendar": [], - "interval": [ - { - "interval": "30s", - "phase": "0s" - } - ], - "excludeCalendar": [], - "startTime": null, - "endTime": null, - "jitter": null, - "timezoneName": "", - "timezoneData": null - }, - "action": { - "startWorkflow": { - "workflowId": "abcd-efgh", - "workflowType": { - "name": "workflow.sanity" - }, - "taskQueue": { - "name": "canary-task-queue", - "kind": "Normal" - }, - "input": null, - "workflowExecutionTimeout": null, - "workflowRunTimeout": null, - "workflowTaskTimeout": null, - "workflowIdReusePolicy": "Unspecified", - "retryPolicy": null, - "cronSchedule": "", - "memo": null, - "searchAttributes": null, - "header": null - } - }, - "policies": { - "overlapPolicy": "Unspecified", - "catchupWindow": null, - "pauseOnFailure": false - }, - "state": { - "notes": "pausing", - "paused": true, - "limitedActions": false, - "remainingActions": "0" - } - }, - "info": { - "actionCount": "10", - "missedCatchupWindow": "0", - "overlapSkipped": "349", - "runningWorkflows": [], - "recentActions": [], - "futureActionTimes": [ - "2022-09-06T19:51:30Z", - "2022-09-06T19:52:00Z", - "2022-09-06T19:52:30Z", - "2022-09-06T19:53:00Z", - "2022-09-06T19:53:30Z", - "2022-09-06T19:54:00Z", - "2022-09-06T19:54:30Z", - "2022-09-06T19:55:00Z", - "2022-09-06T19:55:30Z", - "2022-09-06T19:56:00Z" - ], - "createTime": "2022-09-06T14:49:06.875735012Z", - "updateTime": null, - "invalidScheduleError": "" - }, - "memo": null, - "searchAttributes": null, - "conflictToken": "AAAAAAAAAAI=" -} diff --git a/cypress/fixtures/schedules.json b/cypress/fixtures/schedules.json deleted file mode 100644 index 8c8dea66e..000000000 --- a/cypress/fixtures/schedules.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "schedules": [ - { - "scheduleId": "test2", - "memo": "comments go here", - "searchAttributes": null, - "info": { - "spec": { - "calendar": [], - "interval": [ - { - "interval": "30s", - "phase": "0s" - } - ], - "excludeCalendar": [], - "startTime": null, - "endTime": null, - "jitter": null, - "timezoneName": "", - "timezoneData": null - }, - "workflowType": { - "name": "workflow.sanity" - }, - "notes": "pausing", - "paused": true, - "recentActions": [ - { - "scheduleTime": "2022-09-06T16:24:30Z", - "actualTime": "2022-09-06T16:24:30.045577924Z", - "startWorkflowResult": { - "workflowId": "002c98_Running", - "runId": "54a1a2c3-9322-4f75-88ea-bf5660c251d0" - } - }, - { - "scheduleTime": "2022-09-06T16:43:30Z", - "actualTime": "2022-09-06T16:43:30.026058924Z", - "startWorkflowResult": { - "workflowId": "002c98_ContinuedAsNew", - "runId": "a0dcbea8-c501-4cdc-90cc-44766b19d512" - } - }, - { - "scheduleTime": "2022-09-06T17:02:30Z", - "actualTime": "2022-09-06T17:02:30.066226340Z", - "startWorkflowResult": { - "workflowId": "002c98_TimedOut", - "runId": "75bde3d3-e085-42d2-a41b-066fc1f52097" - } - }, - { - "scheduleTime": "2022-09-06T17:21:30Z", - "actualTime": "2022-09-06T17:21:30.046587715Z", - "startWorkflowResult": { - "workflowId": "002c98_ContinuedAsNew", - "runId": "0ef1dc98-9718-4d8f-80f6-a81f58ecd164" - } - }, - { - "scheduleTime": "2022-09-06T17:40:30Z", - "actualTime": "2022-09-06T17:40:30.055231632Z", - "startWorkflowResult": { - "workflowId": "002c98_Failed", - "runId": "36fa6f53-d2ab-4dc0-a8e0-7037d8a8d369" - } - } - ], - "futureActionTimes": [ - "2022-09-06T19:31:30Z", - "2022-09-06T19:32:00Z", - "2022-09-06T19:32:30Z", - "2022-09-06T19:33:00Z", - "2022-09-06T19:33:30Z" - ] - } - }, - { - "scheduleId": "test1", - "memo": null, - "searchAttributes": null, - "info": { - "spec": { - "calendar": [ - { - "second": "", - "minute": "", - "hour": "", - "dayOfMonth": "", - "month": "", - "year": "", - "dayOfWeek": "" - } - ], - "interval": [], - "excludeCalendar": [], - "startTime": null, - "endTime": null, - "jitter": null, - "timezoneName": "", - "timezoneData": null - }, - "workflowType": { - "name": "cronWorkflow" - }, - "notes": "", - "paused": false, - "recentActions": [], - "futureActionTimes": [ - "2022-09-07T00:00:00Z", - "2022-09-08T00:00:00Z", - "2022-09-09T00:00:00Z", - "2022-09-10T00:00:00Z", - "2022-09-11T00:00:00Z" - ] - } - } - ], - "nextPageToken": null -} diff --git a/cypress/fixtures/search-attributes.json b/cypress/fixtures/search-attributes.json deleted file mode 100644 index 414c82bf6..000000000 --- a/cypress/fixtures/search-attributes.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "keys": { - "BatcherNamespace": "Keyword", - "BatcherUser": "Keyword", - "BinaryChecksums": "Keyword", - "CloseTime": "Datetime", - "CustomBoolField": "Bool", - "CustomDatetimeField": "Datetime", - "CustomDoubleField": "Double", - "CustomIntField": "Int", - "CustomKeywordField": "Keyword", - "CustomStringField": "Text", - "CustomTextField": "Text", - "ExecutionDuration": "Int", - "ExecutionStatus": "Keyword", - "ExecutionTime": "Datetime", - "HistoryLength": "Int", - "RunId": "Keyword", - "StartTime": "Datetime", - "StateTransitionCount": "Int", - "TaskQueue": "Keyword", - "TemporalChangeVersion": "Keyword", - "WorkflowId": "Keyword", - "WorkflowType": "Keyword" - } -} diff --git a/cypress/fixtures/settings.json b/cypress/fixtures/settings.json deleted file mode 100644 index ad6861932..000000000 --- a/cypress/fixtures/settings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Version": "2.0.0", - "Auth": { "Enabled": false, "Options": null }, - "DefaultNamespace": "default", - "DisableWriteActions": false, - "ShowTemporalSystemNamespace": false, - "FeedbackURL": "", - "NotifyOnNewVersion": true, - "Codec": { "Endpoint": "", "PassAccessToken": false } -} diff --git a/cypress/fixtures/settings.write-actions-disabled.json b/cypress/fixtures/settings.write-actions-disabled.json deleted file mode 100644 index 2f4e61a01..000000000 --- a/cypress/fixtures/settings.write-actions-disabled.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Version": "2.0.0", - "Auth": { "Enabled": false, "Options": null }, - "DefaultNamespace": "default", - "DisableWriteActions": true, - "ShowTemporalSystemNamespace": false, - "FeedbackURL": "", - "NotifyOnNewVersion": true, - "Codec": { "Endpoint": "", "PassAccessToken": false } -} diff --git a/cypress/fixtures/user.json b/cypress/fixtures/user.json deleted file mode 100644 index a5a113de3..000000000 --- a/cypress/fixtures/user.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "accessToken": "xxx", - "idToken": "yyy", - "name": "Test User", - "email": "user@test", - "picture": "" -} diff --git a/cypress/fixtures/worker-task-queues.json b/cypress/fixtures/worker-task-queues.json deleted file mode 100644 index bf9761293..000000000 --- a/cypress/fixtures/worker-task-queues.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "pollers": [ - { - "lastAccessTime": "2022-05-04T21:42:46.576609378Z", - "identity": "@poller", - "ratePerSecond": 100000 - } - ], - "taskQueueStatus": null -} diff --git a/cypress/fixtures/workflow-completed.json b/cypress/fixtures/workflow-completed.json deleted file mode 100644 index a025677be..000000000 --- a/cypress/fixtures/workflow-completed.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "executionConfig": { - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "workflowExecutionTimeout": "0s", - "workflowRunTimeout": "0s", - "defaultWorkflowTaskTimeout": "10s" - }, - "workflowExecutionInfo": { - "execution": { - "workflowId": "b12453_Completed", - "runId": "db7b0929-24bc-424c-a935-a1f8da69755e" - }, - "type": { - "name": "RainbowStatusesWorkflow" - }, - "startTime": "2022-04-11T12:59:24.522874755Z", - "closeTime": "2022-04-11T12:59:24.614591797Z", - "status": "Completed", - "historyLength": "11", - "parentNamespaceId": "", - "parentExecution": null, - "executionTime": "2022-04-11T12:59:24.522874755Z", - "memo": { - "fields": {} - }, - "searchAttributes": { - "indexedFields": { - "BinaryChecksums": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "WyIzMmUwNDI1OWUzYTFlMTM3ZmE2Njg5M2JiNjE3OTc5YSJd" - }, - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "ZmFsc2U=" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDQtMTFUMTI6NTk6MjQuNTIzMzA5WiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "MQ==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "MQ==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtYjEyNDUzIg==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgYjEyNDUzIENvbXBsZXRlZCI=" - } - } - }, - "autoResetPoints": { - "points": [ - { - "binaryChecksum": "32e04259e3a1e137fa66893bb617979a", - "runId": "db7b0929-24bc-424c-a935-a1f8da69755e", - "firstWorkflowTaskCompletedId": "4", - "createTime": "2022-04-11T12:59:24.554244672Z", - "expireTime": null, - "resettable": true - } - ] - }, - "taskQueue": "rainbow-statuses", - "stateTransitionCount": "7" - }, - "pendingActivities": [], - "pendingChildren": [], - "pendingWorkflowTask": null -} diff --git a/cypress/fixtures/workflow-running.json b/cypress/fixtures/workflow-running.json deleted file mode 100644 index 4384d071f..000000000 --- a/cypress/fixtures/workflow-running.json +++ /dev/null @@ -1,117 +0,0 @@ -{ - "executionConfig": { - "taskQueue": { - "name": "rainbow-statuses", - "kind": "Normal" - }, - "workflowExecutionTimeout": "0s", - "workflowRunTimeout": "0s", - "defaultWorkflowTaskTimeout": "10s" - }, - "workflowExecutionInfo": { - "execution": { - "workflowId": "09db15_Running", - "runId": "4284ef9a-947f-4db2-bf15-dbc377e71fa6" - }, - "type": { - "name": "RainbowStatusesWorkflow" - }, - "startTime": "2022-04-28T05:50:48.264756929Z", - "closeTime": null, - "status": "Running", - "historyLength": "6", - "parentNamespaceId": "", - "parentExecution": null, - "executionTime": "2022-04-28T05:50:48.264756929Z", - "memo": { - "fields": {} - }, - "searchAttributes": { - "indexedFields": { - "BinaryChecksums": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "WyIzN2VhNzVjMzc4MDFjMTY2N2IyOThlNGYxZjk1NWE3MCJd" - }, - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "dHJ1ZQ==" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDQtMjhUMDU6NTA6NDguMjYzNTE2MDVaIg==" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "MA==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "MA==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtMDlkYjE1Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgMDlkYjE1IFJ1bm5pbmci" - } - } - }, - "autoResetPoints": { - "points": [ - { - "binaryChecksum": "37ea75c37801c1667b298e4f1f955a70", - "runId": "4284ef9a-947f-4db2-bf15-dbc377e71fa6", - "firstWorkflowTaskCompletedId": "5", - "createTime": "2022-04-28T05:50:48.291718480Z", - "expireTime": null, - "resettable": true - } - ] - }, - "taskQueue": "rainbow-statuses", - "stateTransitionCount": "5" - }, - "pendingActivities": [ - { - "activityId": "6", - "activityType": { - "name": "LongActivity" - }, - "state": "Started", - "heartbeatDetails": null, - "lastHeartbeatTime": "2022-04-28T05:50:48.306477858Z", - "lastStartedTime": "2022-04-28T05:50:48.306477858Z", - "attempt": 1, - "maximumAttempts": 1, - "scheduledTime": null, - "expirationTime": "0001-01-01T00:00:00Z", - "lastFailure": null, - "lastWorkerIdentity": "173471@user0@" - } - ], - "pendingChildren": [], - "pendingWorkflowTask": null -} diff --git a/cypress/fixtures/workflows.json b/cypress/fixtures/workflows.json deleted file mode 100644 index d4328410b..000000000 --- a/cypress/fixtures/workflows.json +++ /dev/null @@ -1,1163 +0,0 @@ -{ - "executions": [ - { - "execution": { - "workflowId": "002c98_Running", - "runId": "54a1a2c3-9322-4f75-88ea-bf5660c251d0" - }, - "type": { - "name": "ImportantWorkflowType" - }, - "startTime": "2022-03-23T18:06:01.726484047Z", - "closeTime": null, - "status": "Running", - "historyLength": "0", - "parentNamespaceId": "", - "parentExecution": null, - "executionTime": "2022-03-23T18:06:01.726484047Z", - "memo": { - "fields": {} - }, - "searchAttributes": { - "indexedFields": { - "BinaryChecksums": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "WyIzMmUwNDI1OWUzYTFlMTM3ZmE2Njg5M2JiNjE3OTc5YSJd" - }, - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "dHJ1ZQ==" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDMtMjNUMTg6MDY6MDEuNzIwMTM3WiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "MA==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "MA==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtMDAyYzk4Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgMDAyYzk4IFJ1bm5pbmci" - } - } - }, - "autoResetPoints": null, - "taskQueue": "rainbow-statuses", - "stateTransitionCount": "0" - }, - { - "execution": { - "workflowId": "002c98_ContinuedAsNew", - "runId": "a0dcbea8-c501-4cdc-90cc-44766b19d512" - }, - "type": { - "name": "ImportantWorkflowType" - }, - "startTime": "2022-03-23T18:06:01.874404131Z", - "closeTime": "2022-03-23T18:06:26.011906253Z", - "status": "Terminated", - "historyLength": "3", - "parentNamespaceId": "", - "parentExecution": null, - "executionTime": "2022-03-23T18:06:01.874404131Z", - "memo": { - "fields": {} - }, - "searchAttributes": { - "indexedFields": { - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "ZmFsc2U=" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDMtMjNUMTg6MDY6MDEuODIzNzk0WiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "NQ==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "NQ==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtMDAyYzk4Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgMDAyYzk4IENvbnRpbnVlZEFzTmV3Ig==" - } - } - }, - "autoResetPoints": null, - "taskQueue": "rainbow-statuses", - "stateTransitionCount": "2" - }, - { - "execution": { - "workflowId": "002c98_TimedOut", - "runId": "75bde3d3-e085-42d2-a41b-066fc1f52097" - }, - "type": { - "name": "ImportantWorkflowType" - }, - "startTime": "2022-03-23T18:06:01.834286089Z", - "closeTime": "2022-03-23T18:06:02.861970423Z", - "status": "TimedOut", - "historyLength": "3", - "parentNamespaceId": "", - "parentExecution": null, - "executionTime": "2022-03-23T18:06:01.834286089Z", - "memo": { - "fields": {} - }, - "searchAttributes": { - "indexedFields": { - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "dHJ1ZQ==" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDMtMjNUMTg6MDY6MDEuODMxOTE4WiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "Ng==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "Ng==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtMDAyYzk4Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgMDAyYzk4IFRpbWVkT3V0Ig==" - } - } - }, - "autoResetPoints": null, - "taskQueue": "rainbow-statuses", - "stateTransitionCount": "2" - }, - { - "execution": { - "workflowId": "002c98_ContinuedAsNew", - "runId": "0ef1dc98-9718-4d8f-80f6-a81f58ecd164" - }, - "type": { - "name": "ImportantWorkflowType" - }, - "startTime": "2022-03-23T18:06:01.825801964Z", - "closeTime": "2022-03-23T18:06:01.874404131Z", - "status": "ContinuedAsNew", - "historyLength": "5", - "parentNamespaceId": "", - "parentExecution": null, - "executionTime": "2022-03-23T18:06:01.825801964Z", - "memo": { - "fields": {} - }, - "searchAttributes": { - "indexedFields": { - "BinaryChecksums": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "WyIzMmUwNDI1OWUzYTFlMTM3ZmE2Njg5M2JiNjE3OTc5YSJd" - }, - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "ZmFsc2U=" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDMtMjNUMTg6MDY6MDEuODIzNzk0WiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "NQ==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "NQ==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtMDAyYzk4Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgMDAyYzk4IENvbnRpbnVlZEFzTmV3Ig==" - } - } - }, - "autoResetPoints": null, - "taskQueue": "rainbow-statuses", - "stateTransitionCount": "3" - }, - { - "execution": { - "workflowId": "002c98_Failed", - "runId": "36fa6f53-d2ab-4dc0-a8e0-7037d8a8d369" - }, - "type": { - "name": "ImportantWorkflowType" - }, - "startTime": "2022-03-23T18:06:01.767867797Z", - "closeTime": "2022-03-23T18:06:01.823839297Z", - "status": "Failed", - "historyLength": "11", - "parentNamespaceId": "", - "parentExecution": null, - "executionTime": "2022-03-23T18:06:01.767867797Z", - "memo": { - "fields": {} - }, - "searchAttributes": { - "indexedFields": { - "BinaryChecksums": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "WyIzMmUwNDI1OWUzYTFlMTM3ZmE2Njg5M2JiNjE3OTc5YSJd" - }, - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "dHJ1ZQ==" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDMtMjNUMTg6MDY6MDEuNzYyMDAyWiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "Mg==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "Mg==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtMDAyYzk4Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgMDAyYzk4IEZhaWxlZCI=" - } - } - }, - "autoResetPoints": null, - "taskQueue": "rainbow-statuses", - "stateTransitionCount": "7" - }, - { - "execution": { - "workflowId": "002c98_Terminated", - "runId": "7a5d2860-91b9-4f18-b406-dc9b7f03e0fa" - }, - "type": { - "name": "ImportantWorkflowType" - }, - "startTime": "2022-03-23T18:06:01.797946631Z", - "closeTime": "2022-03-23T18:06:01.816783797Z", - "status": "Terminated", - "historyLength": "3", - "parentNamespaceId": "", - "parentExecution": null, - "executionTime": "2022-03-23T18:06:01.797946631Z", - "memo": { - "fields": {} - }, - "searchAttributes": { - "indexedFields": { - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "dHJ1ZQ==" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDMtMjNUMTg6MDY6MDEuNzkxMDc3WiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "NA==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "NA==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtMDAyYzk4Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgMDAyYzk4IFRlcm1pbmF0ZWQi" - } - } - }, - "autoResetPoints": null, - "taskQueue": "rainbow-statuses", - "stateTransitionCount": "2" - }, - { - "execution": { - "workflowId": "002c98_Completed", - "runId": "de64f5d9-a6fe-476b-8825-92d4c8fea39d" - }, - "type": { - "name": "ImportantWorkflowType" - }, - "startTime": "2022-03-23T18:06:01.757428214Z", - "closeTime": "2022-03-23T18:06:01.815125881Z", - "status": "Completed", - "historyLength": "11", - "parentNamespaceId": "", - "parentExecution": null, - "executionTime": "2022-03-23T18:06:01.757428214Z", - "memo": { - "fields": {} - }, - "searchAttributes": { - "indexedFields": { - "BinaryChecksums": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "WyIzMmUwNDI1OWUzYTFlMTM3ZmE2Njg5M2JiNjE3OTc5YSJd" - }, - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "ZmFsc2U=" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDMtMjNUMTg6MDY6MDEuNzU0NTkzWiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "MQ==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "MQ==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtMDAyYzk4Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgMDAyYzk4IENvbXBsZXRlZCI=" - } - } - }, - "autoResetPoints": null, - "taskQueue": "rainbow-statuses", - "stateTransitionCount": "7" - }, - { - "execution": { - "workflowId": "002c98_Canceled", - "runId": "43a1484f-90fa-4265-95f4-8165a1d57b14" - }, - "type": { - "name": "ImportantWorkflowType" - }, - "startTime": "2022-03-23T18:06:01.777287172Z", - "closeTime": "2022-03-23T18:06:01.799289006Z", - "status": "Canceled", - "historyLength": "8", - "parentNamespaceId": "", - "parentExecution": null, - "executionTime": "2022-03-23T18:06:01.777287172Z", - "memo": { - "fields": {} - }, - "searchAttributes": { - "indexedFields": { - "BinaryChecksums": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "WyIzMmUwNDI1OWUzYTFlMTM3ZmE2Njg5M2JiNjE3OTc5YSJd" - }, - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "ZmFsc2U=" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDMtMjNUMTg6MDY6MDEuNzczNDcyWiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "Mw==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "Mw==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtMDAyYzk4Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgMDAyYzk4IENhbmNlbGVkIg==" - } - } - }, - "autoResetPoints": null, - "taskQueue": "rainbow-statuses", - "stateTransitionCount": "4" - }, - { - "execution": { - "workflowId": "1337c9_Running", - "runId": "54a1a2c3-9322-4f75-88ea-bf5660c251d0" - }, - "type": { - "name": "AnotherWorkflowType" - }, - "startTime": "2022-03-23T18:06:01.726484047Z", - "closeTime": null, - "status": "Running", - "historyLength": "0", - "parentNamespaceId": "", - "parentExecution": null, - "executionTime": "2022-03-23T18:06:01.726484047Z", - "memo": { - "fields": {} - }, - "searchAttributes": { - "indexedFields": { - "BinaryChecksums": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "WyIzMmUwNDI1OWUzYTFlMTM3ZmE2Njg5M2JiNjE3OTc5YSJd" - }, - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "dHJ1ZQ==" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDMtMjNUMTg6MDY6MDEuNzIwMTM3WiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "MA==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "MA==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtMDAyYzk4Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgMDAyYzk4IFJ1bm5pbmci" - } - } - }, - "autoResetPoints": null, - "taskQueue": "rainbow-statuses", - "stateTransitionCount": "0" - }, - { - "execution": { - "workflowId": "1337c9_ContinuedAsNew", - "runId": "a0dcbea8-c501-4cdc-90cc-44766b19d512" - }, - "type": { - "name": "AnotherWorkflowType" - }, - "startTime": "2022-03-23T18:06:01.874404131Z", - "closeTime": "2022-03-23T18:06:26.011906253Z", - "status": "Terminated", - "historyLength": "3", - "parentNamespaceId": "", - "parentExecution": null, - "executionTime": "2022-03-23T18:06:01.874404131Z", - "memo": { - "fields": {} - }, - "searchAttributes": { - "indexedFields": { - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "ZmFsc2U=" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDMtMjNUMTg6MDY6MDEuODIzNzk0WiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "NQ==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "NQ==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtMDAyYzk4Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgMDAyYzk4IENvbnRpbnVlZEFzTmV3Ig==" - } - } - }, - "autoResetPoints": null, - "taskQueue": "rainbow-statuses", - "stateTransitionCount": "2" - }, - { - "execution": { - "workflowId": "1337c9_TimedOut", - "runId": "75bde3d3-e085-42d2-a41b-066fc1f52097" - }, - "type": { - "name": "AnotherWorkflowType" - }, - "startTime": "2022-03-23T18:06:01.834286089Z", - "closeTime": "2022-03-23T18:06:02.861970423Z", - "status": "TimedOut", - "historyLength": "3", - "parentNamespaceId": "", - "parentExecution": null, - "executionTime": "2022-03-23T18:06:01.834286089Z", - "memo": { - "fields": {} - }, - "searchAttributes": { - "indexedFields": { - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "dHJ1ZQ==" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDMtMjNUMTg6MDY6MDEuODMxOTE4WiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "Ng==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "Ng==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtMDAyYzk4Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgMDAyYzk4IFRpbWVkT3V0Ig==" - } - } - }, - "autoResetPoints": null, - "taskQueue": "rainbow-statuses", - "stateTransitionCount": "2" - }, - { - "execution": { - "workflowId": "1337c9_ContinuedAsNew", - "runId": "0ef1dc98-9718-4d8f-80f6-a81f58ecd164" - }, - "type": { - "name": "AnotherWorkflowType" - }, - "startTime": "2022-03-23T18:06:01.825801964Z", - "closeTime": "2022-03-23T18:06:01.874404131Z", - "status": "ContinuedAsNew", - "historyLength": "5", - "parentNamespaceId": "", - "parentExecution": null, - "executionTime": "2022-03-23T18:06:01.825801964Z", - "memo": { - "fields": {} - }, - "searchAttributes": { - "indexedFields": { - "BinaryChecksums": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "WyIzMmUwNDI1OWUzYTFlMTM3ZmE2Njg5M2JiNjE3OTc5YSJd" - }, - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "ZmFsc2U=" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDMtMjNUMTg6MDY6MDEuODIzNzk0WiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "NQ==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "NQ==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtMDAyYzk4Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgMDAyYzk4IENvbnRpbnVlZEFzTmV3Ig==" - } - } - }, - "autoResetPoints": null, - "taskQueue": "rainbow-statuses", - "stateTransitionCount": "3" - }, - { - "execution": { - "workflowId": "1337c9_Failed", - "runId": "36fa6f53-d2ab-4dc0-a8e0-7037d8a8d369" - }, - "type": { - "name": "AnotherWorkflowType" - }, - "startTime": "2022-03-23T18:06:01.767867797Z", - "closeTime": "2022-03-23T18:06:01.823839297Z", - "status": "Failed", - "historyLength": "11", - "parentNamespaceId": "", - "parentExecution": null, - "executionTime": "2022-03-23T18:06:01.767867797Z", - "memo": { - "fields": {} - }, - "searchAttributes": { - "indexedFields": { - "BinaryChecksums": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "WyIzMmUwNDI1OWUzYTFlMTM3ZmE2Njg5M2JiNjE3OTc5YSJd" - }, - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "dHJ1ZQ==" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDMtMjNUMTg6MDY6MDEuNzYyMDAyWiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "Mg==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "Mg==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtMDAyYzk4Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgMDAyYzk4IEZhaWxlZCI=" - } - } - }, - "autoResetPoints": null, - "taskQueue": "rainbow-statuses", - "stateTransitionCount": "7" - }, - { - "execution": { - "workflowId": "1337c9_Terminated", - "runId": "7a5d2860-91b9-4f18-b406-dc9b7f03e0fa" - }, - "type": { - "name": "AnotherWorkflowType" - }, - "startTime": "2022-03-23T18:06:01.797946631Z", - "closeTime": "2022-03-23T18:06:01.816783797Z", - "status": "Terminated", - "historyLength": "3", - "parentNamespaceId": "", - "parentExecution": null, - "executionTime": "2022-03-23T18:06:01.797946631Z", - "memo": { - "fields": {} - }, - "searchAttributes": { - "indexedFields": { - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "dHJ1ZQ==" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDMtMjNUMTg6MDY6MDEuNzkxMDc3WiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "NA==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "NA==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtMDAyYzk4Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgMDAyYzk4IFRlcm1pbmF0ZWQi" - } - } - }, - "autoResetPoints": null, - "taskQueue": "rainbow-statuses", - "stateTransitionCount": "2" - }, - { - "execution": { - "workflowId": "1337c9_Completed", - "runId": "de64f5d9-a6fe-476b-8825-92d4c8fea39d" - }, - "type": { - "name": "AnotherWorkflowType" - }, - "startTime": "2022-03-23T18:06:01.757428214Z", - "closeTime": "2022-03-23T18:06:01.815125881Z", - "status": "Completed", - "historyLength": "11", - "parentNamespaceId": "", - "parentExecution": null, - "executionTime": "2022-03-23T18:06:01.757428214Z", - "memo": { - "fields": {} - }, - "searchAttributes": { - "indexedFields": { - "BinaryChecksums": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "WyIzMmUwNDI1OWUzYTFlMTM3ZmE2Njg5M2JiNjE3OTc5YSJd" - }, - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "ZmFsc2U=" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDMtMjNUMTg6MDY6MDEuNzU0NTkzWiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "MQ==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "MQ==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtMDAyYzk4Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgMDAyYzk4IENvbXBsZXRlZCI=" - } - } - }, - "autoResetPoints": null, - "taskQueue": "rainbow-statuses", - "stateTransitionCount": "7" - }, - { - "execution": { - "workflowId": "1337c9_Canceled", - "runId": "43a1484f-90fa-4265-95f4-8165a1d57b14" - }, - "type": { - "name": "AnotherWorkflowType" - }, - "startTime": "2022-03-23T18:06:01.777287172Z", - "closeTime": "2022-03-23T18:06:01.799289006Z", - "status": "Canceled", - "historyLength": "8", - "parentNamespaceId": "", - "parentExecution": null, - "executionTime": "2022-03-23T18:06:01.777287172Z", - "memo": { - "fields": {} - }, - "searchAttributes": { - "indexedFields": { - "BinaryChecksums": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "WyIzMmUwNDI1OWUzYTFlMTM3ZmE2Njg5M2JiNjE3OTc5YSJd" - }, - "CustomBoolField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "Qm9vbA==" - }, - "data": "ZmFsc2U=" - }, - "CustomDatetimeField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RGF0ZXRpbWU=" - }, - "data": "IjIwMjItMDMtMjNUMTg6MDY6MDEuNzczNDcyWiI=" - }, - "CustomDoubleField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "RG91Ymxl" - }, - "data": "Mw==" - }, - "CustomIntField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "SW50" - }, - "data": "Mw==" - }, - "CustomKeywordField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "S2V5d29yZA==" - }, - "data": "InJhaW5ib3ctc3RhdHVzZXMtMDAyYzk4Ig==" - }, - "CustomStringField": { - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - "type": "VGV4dA==" - }, - "data": "InJhaW5ib3cgc3RhdHVzZXMgMDAyYzk4IENhbmNlbGVkIg==" - } - } - }, - "autoResetPoints": null, - "taskQueue": "rainbow-statuses", - "stateTransitionCount": "4" - } - ], - "nextPageToken": null -} diff --git a/cypress/integration/banner.spec.js b/cypress/integration/banner.spec.js deleted file mode 100644 index 570b3c779..000000000 --- a/cypress/integration/banner.spec.js +++ /dev/null @@ -1,55 +0,0 @@ -import cluster from '../fixtures/cluster.json'; - -describe('Banner', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.visit('/'); - }); - - it('should show Temporal New Version banner', () => { - cy.get('[data-cy=temporal-version-banner]').should('be.visible'); - }); - - it('should show UI New Version banner', () => { - cy.get('[data-cy=temporal-version-banner]').should('be.visible'); - cy.get('[data-cy=close-banner]').click(); - - cy.get('[data-cy=temporal-version-banner]').should('not.exist'); - cy.get('[data-cy=ui-version-banner]').should('be.visible'); - }); - - it('after closing banner, it should not be visible for the same version', () => { - cy.get('[data-cy=temporal-version-banner]').should('be.visible'); - cy.get('[data-cy=close-banner]').click(); - - cy.visit('/'); - - cy.get('[data-cy=temporal-version-banner]').should('not.exist'); - }); - - it('after closing banner, it should be visible when upgraded to a newer version', () => { - cy.intercept(Cypress.env('VITE_API_HOST') + '/api/v1/cluster*', { - ...cluster, - versionInfo: { - current: { version: '1.15.0' }, - recommended: { version: '1.16.0' }, - }, - }).as('cluster-api'); - cy.get('[data-cy=temporal-version-banner]').should('be.visible'); - cy.get('[data-cy=close-banner]').click(); - cy.get('[data-cy=temporal-version-banner]').should('not.exist'); - - cy.intercept(Cypress.env('VITE_API_HOST') + '/api/v1/cluster*', { - ...cluster, - versionInfo: { - current: { version: '1.16.0' }, - recommended: { version: '1.17.0' }, - }, - }).as('cluster-api'); - - cy.visit('/'); - - cy.get('[data-cy=temporal-version-banner]').should('be.visible'); - }); -}); diff --git a/cypress/integration/data-encoder-settings.spec.js b/cypress/integration/data-encoder-settings.spec.js deleted file mode 100644 index 096db7f8a..000000000 --- a/cypress/integration/data-encoder-settings.spec.js +++ /dev/null @@ -1,210 +0,0 @@ -describe('Set Data Encoder Settings', () => { - describe('Data Encoder without site setting codec endpoint', () => { - beforeEach(() => { - cy.interceptApi(); - cy.clearLocalStorage(); - - cy.visit('/namespaces/default/workflows'); - - cy.fixture('namespaces.json') - .then(({ namespaces }) => { - return namespaces - .map((n) => n.namespaceInfo.name) - .filter((name) => name !== 'temporal-system'); - }) - .as('namespaces'); - - cy.wait('@workflows-api'); - cy.wait('@namespaces-api'); - - cy.get('[data-cy="navigation-header"]').as('header'); - }); - - it('Enter invalid endpoint to show error and enter valid enpoint with confirm', () => { - cy.get('@header').find('[data-cy="data-encoder-status"]').click(); - cy.get('[data-cy="data-encoder-title"]').contains('Data Encoder'); - cy.get('[data-cy="data-encoder-endpoint-title"]').contains( - 'Remote Codec Endpoint', - ); - cy.get('[data-cy="data-encoder-endpoint-input"]').should( - 'have.value', - '', - ); - cy.get('[data-cy="data-encoder-port-input"]').should('have.value', ''); - - // Set invalid endpoint and get error - cy.get('[data-cy="data-encoder-endpoint-input"]').type('abc123'); - - cy.get('[data-cy="data-encoder-endpoint-error"]').contains( - 'Endpoint must start with http:// or https://', - ); - - cy.get('[data-cy="confirm-modal-button"]').should('be.disabled'); - - // Clear endpoint and set valid endpoint - cy.get('[data-cy="data-encoder-endpoint-input"]').clear(); - cy.get('[data-cy="data-encoder-endpoint-input"]').type('http://test.com'); - cy.get('[data-cy="confirm-modal-button"]').click(); - - cy.get('@header') - .find('[data-cy="data-encoder-status-configured"]') - .should('be.visible'); - cy.get('@header') - .find('[data-cy="data-encoder-status-configured"]') - .click(); - - cy.get('[data-cy="data-encoder-endpoint-input"]').should( - 'have.value', - 'http://test.com', - ); - }); - - it('Check pass access token and enter invalid endpoint to show error and enter valid enpoint with confirm', () => { - cy.get('@header').find('[data-cy="data-encoder-status"]').click(); - cy.get('[data-cy="data-encoder-title"]').contains('Data Encoder'); - cy.get('[data-cy="data-encoder-endpoint-title"]').contains( - 'Remote Codec Endpoint', - ); - cy.get('[data-cy="data-encoder-endpoint-input"]').should( - 'have.value', - '', - ); - cy.get('[data-cy="data-encoder-port-input"]').should('have.value', ''); - - // Set pass access token to true - cy.get('.checkmark').click(); - - cy.get('[data-cy="data-encoder-endpoint-error"]').contains( - 'Endpoint must be https:// if passing access token', - ); - - cy.get('[data-cy="data-encoder-endpoint-input"]').type('http://test.com'); - cy.get('[data-cy="confirm-modal-button"]').should('be.disabled'); - - cy.get('[data-cy="data-encoder-endpoint-error"]').contains( - 'Endpoint must be https:// if passing access token', - ); - - cy.get('[data-cy="data-encoder-endpoint-input"]').clear(); - cy.get('[data-cy="data-encoder-endpoint-input"]').type( - 'https://test.com', - ); - cy.get('[data-cy="confirm-modal-button"]').click(); - - cy.get('@header') - .find('[data-cy="data-encoder-status-configured"]') - .should('be.visible'); - cy.get('@header') - .find('[data-cy="data-encoder-status-configured"]') - .click(); - - cy.get('[data-cy="data-encoder-endpoint-input"]').should( - 'have.value', - 'https://test.com', - ); - }); - - it('Enter port with confirm', () => { - cy.get('@header').find('[data-cy="data-encoder-status"]').click(); - cy.get('[data-cy="data-encoder-title"]').contains('Data Encoder'); - cy.get('[data-cy="data-encoder-endpoint-title"]').contains( - 'Remote Codec Endpoint', - ); - cy.get('[data-cy="data-encoder-endpoint-input"]').should( - 'have.value', - '', - ); - cy.get('[data-cy="data-encoder-port-input"]').should('have.value', ''); - - cy.get('[data-cy="data-encoder-port-input"]').type('3456'); - cy.get('[data-cy="confirm-modal-button"]').click(); - - cy.get('@header') - .find('[data-cy="data-encoder-status-configured"]') - .should('be.visible'); - - cy.get('@header') - .find('[data-cy="data-encoder-status-configured"]') - .click(); - cy.get('[data-cy="data-encoder-port-input"]').should( - 'have.value', - '3456', - ); - }); - }); - - describe('Data Encoder with site setting codec endpoint', () => { - beforeEach(() => { - cy.interceptApi(); - cy.clearLocalStorage(); - - cy.intercept(Cypress.env('VITE_API_HOST') + '/api/v1/settings*', { - Auth: { Enabled: false, Options: null }, - Codec: { - Endpoint: 'http://www.site-setting.com', - }, - }).as('settings-api'); - - cy.visit('/namespaces/default/workflows'); - - cy.fixture('namespaces.json') - .then(({ namespaces }) => { - return namespaces - .map((n) => n.namespaceInfo.name) - .filter((name) => name !== 'temporal-system'); - }) - .as('namespaces'); - - cy.wait('@workflows-api'); - cy.wait('@namespaces-api'); - - cy.get('[data-cy="navigation-header"]').as('header'); - }); - - it('Enter invalid endpoint to show error and enter valid enpoint with confirm', () => { - cy.get('@header') - .find('[data-cy="data-encoder-status-configured"]') - .click(); - cy.get('[data-cy="data-encoder-title"]').contains('Data Encoder'); - cy.get('[data-cy="data-encoder-endpoint-title"]').contains( - 'Remote Codec Endpoint', - ); - - cy.get('[data-cy="data-encoder-site-endpoint"]').contains( - 'http://www.site-setting.com', - ); - - cy.get('[data-cy="data-encoder-endpoint-input"]').should( - 'have.value', - '', - ); - cy.get('[data-cy="data-encoder-port-input"]').should('have.value', ''); - - // Set invalid endpoint and get error - cy.get('[data-cy="data-encoder-endpoint-input"]').type('abc123'); - - cy.get('[data-cy="data-encoder-endpoint-error"]').contains( - 'Endpoint must start with http:// or https://', - ); - - cy.get('[data-cy="confirm-modal-button"]').should('be.disabled'); - - // Clear endpoint and set valid endpoint - cy.get('[data-cy="data-encoder-endpoint-input"]').clear(); - cy.get('[data-cy="data-encoder-endpoint-input"]').type('http://test.com'); - cy.get('[data-cy="confirm-modal-button"]').click(); - - cy.get('@header') - .find('[data-cy="data-encoder-status-configured"]') - .should('be.visible'); - cy.get('@header') - .find('[data-cy="data-encoder-status-configured"]') - .click(); - - cy.get('[data-cy="data-encoder-endpoint-input"]').should( - 'have.value', - 'http://test.com', - ); - }); - }); -}); diff --git a/cypress/integration/event-history-empty-states.spec.js b/cypress/integration/event-history-empty-states.spec.js deleted file mode 100644 index 0c0ff5a03..000000000 --- a/cypress/integration/event-history-empty-states.spec.js +++ /dev/null @@ -1,49 +0,0 @@ -/// - -import workflowsFixture from '../fixtures/workflows.json'; - -const workflow = workflowsFixture.executions[0]; -const { workflowId, runId } = workflow.execution; -const workflowUrl = `/namespaces/default/workflows/${workflowId}/${runId}`; - -describe('Workflow Executions List', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/*/runs/*/events/reverse?`, - { fixture: 'event-history-empty.json' }, - ).as('event-history-api'); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/*/runs/*?`, - { fixture: 'workflow-completed.json' }, - ).as('workflow-api'); - }); - - for (const view of ['feed', 'compact']) { - it(`should show an empty state in the ${view} view`, () => { - cy.visit(workflowUrl + `/history/${view}`); - - cy.wait('@workflow-api'); - cy.wait('@event-history-api'); - cy.wait('@query-api'); - - cy.contains('No Events Match'); - }); - } - - it.only('should display a custom empty state if there are events, but no event groups', () => { - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/*/runs/*/events*`, - { fixture: 'event-history-with-no-activities.json' }, - ).as('event-history-api'); - - cy.visit(workflowUrl + `/history/compact`); - - cy.contains('No Events Match'); - }); -}); diff --git a/cypress/integration/event-history.spec.js b/cypress/integration/event-history.spec.js deleted file mode 100644 index 1af9633a3..000000000 --- a/cypress/integration/event-history.spec.js +++ /dev/null @@ -1,153 +0,0 @@ -/// - -import { formatDistanceToNow } from 'date-fns'; -import * as dateTz from 'date-fns-tz'; - -import workflowCompletedFixture from '../fixtures/workflow-completed.json'; -import eventsFixtureDescending from '../fixtures/event-history-completed-reverse.json'; -import eventsFixtureAscending from '../fixtures/event-history-completed.json'; - -const [firstEventInDescendingOrder] = eventsFixtureDescending.history.events; - -const { workflowId, runId } = - workflowCompletedFixture.workflowExecutionInfo.execution; - -describe('Workflow Events', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/*/runs/*/events/reverse*`, - { fixture: 'event-history-completed-reverse.json' }, - ).as('event-history-descending'); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/*/runs/*/events?`, - { fixture: 'event-history-completed.json' }, - ).as('event-history-ascending'); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/${workflowId}/runs/${runId}?`, - { fixture: 'workflow-completed.json' }, - ).as('workflow-api'); - - cy.visit('/namespaces/default/workflows'); - - cy.wait('@workflows-api'); - cy.wait('@namespaces-api'); - }); - - it('default to the summary page when visiting a workflow', () => { - cy.clearLocalStorage(); - - cy.visit(`/namespaces/default/workflows/${workflowId}/${runId}`); - - cy.wait('@workflow-api'); - cy.wait('@event-history-descending'); - - cy.url().should('contain', '/feed'); - }); - - it('default to last viewed event view when visiting a workflow', () => { - cy.visit(`/namespaces/default/workflows/${workflowId}/${runId}`); - - cy.wait('@workflow-api'); - cy.wait('@event-history-descending'); - - cy.url().should('contain', '/feed'); - - cy.get('[data-cy="feed"]').click(); - cy.url().should('contain', '/feed'); - - cy.visit('/namespaces/default/workflows'); - - cy.visit(`/namespaces/default/workflows/${workflowId}/${runId}`); - cy.url().should('contain', '/feed'); - }); - - it('should render events in feed view', () => { - cy.visit( - `/namespaces/default/workflows/${workflowId}/${runId}/history/feed`, - ); - - cy.wait('@workflow-api'); - cy.wait('@event-history-descending'); - - cy.get('[data-cy="event-summary-row"]').should( - 'have.length', - eventsFixtureDescending.history.events.length, - ); - - cy.get('[data-cy="event-summary-table"]').contains( - firstEventInDescendingOrder.eventId, - ); - }); - - it('should render event time in various formats', () => { - cy.visit( - `/namespaces/default/workflows/${workflowId}/${runId}/history/feed`, - ); - - cy.wait('@workflow-api'); - cy.wait('@event-history-descending'); - - const dt = new Date(eventsFixtureDescending.history.events[0].eventTime); - - cy.get( - '[data-cy="event-summary-table-header-desktop"] [data-cy=event-date-filter-button]', - ).click(); - cy.get( - '[data-cy="event-summary-table-header-desktop"] [data-cy=event-date-filter-relative]', - ).click(); - const relative = formatDistanceToNow(dt); - cy.get('[data-cy="event-summary-table"]').contains(relative); - - cy.get( - '[data-cy="event-summary-table-header-desktop"] [data-cy=event-date-filter-button]', - ).click(); - cy.get( - '[data-cy="event-summary-table-header-desktop"] [data-cy=event-date-filter-UTC]', - ).click(); - const utc = dateTz.formatInTimeZone(dt, 'UTC', 'yyyy-MM-dd z HH:mm:ss.SS'); - cy.get('[data-cy="event-summary-table"]').contains(utc); - - cy.get( - '[data-cy="event-summary-table-header-desktop"] [data-cy=event-date-filter-button]', - ).click(); - cy.get( - '[data-cy="event-summary-table-header-desktop"] [data-cy=event-date-filter-local]', - ).click(); - const local = dateTz.format(dt, 'yyyy-MM-dd z HH:mm:ss.SS'); - cy.get('[data-cy="event-summary-table"]').contains(local); - }); - - it('should render events in compact view', () => { - cy.visit( - `/namespaces/default/workflows/${workflowId}/${runId}/history/compact`, - ); - - cy.wait('@workflow-api'); - cy.wait('@event-history-descending'); - - cy.get('[data-cy="event-summary-row"]') - .should('not.have.length', 0) - .should('not.have.length', eventsFixtureDescending.history.events.length); - cy.get('[data-cy="event-summary-table"]').contains('activity.timeout'); - }); - - it('should be viewable as JSON', () => { - cy.visit(`/namespaces/default/workflows/${workflowId}/${runId}`); - - cy.wait('@workflow-api'); - - cy.get('[data-cy="json"]').click(); - - cy.wait('@event-history-ascending'); - - const match = eventsFixtureAscending.history.events[0].eventTime; - cy.get('[data-cy="event-history-json"]').contains(match); - }); -}); diff --git a/cypress/integration/event-ordering-fallback-for-older-versions.spec.js b/cypress/integration/event-ordering-fallback-for-older-versions.spec.js deleted file mode 100644 index e59cd9d87..000000000 --- a/cypress/integration/event-ordering-fallback-for-older-versions.spec.js +++ /dev/null @@ -1,66 +0,0 @@ -/// - -const visitWorkflow = (suffix = '') => { - cy.visit( - `/namespaces/default/workflows/workflowId/runId/history/feed${suffix}`, - ); -}; - -describe('Fallback to Ascending Ordering of Event History on Older Versions of Temporal Server', () => { - beforeEach(() => { - cy.interceptNamespacesApi(); - cy.interceptGithubReleasesApi(); - cy.interceptQueryApi(); - cy.interceptTaskQueuesApi(); - cy.interceptSettingsApi(); - cy.interceptWorkflowApi(); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - '/api/v1/namespaces/default/workflows/workflowId/runs/runId/events?', - { fixture: 'event-history-completed.json' }, - ).as('events-ascending-api'); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - '/api/v1/namespaces/default/workflows/workflowId/runs/runId/events/reverse?', - { fixture: 'event-history-completed-reverse.json' }, - ).as('events-descending-api'); - }); - - it('should default to sorting events in descending order if on a modern version', () => { - cy.interceptClusterApi(); - visitWorkflow(); - cy.wait('@events-descending-api'); - }); - - it('should sort events in ascending if a query param is set', () => { - cy.interceptClusterApi(); - visitWorkflow('?sort=ascending'); - cy.wait('@events-ascending-api'); - }); - - it('should sort events in descending if a query param is set', () => { - cy.interceptClusterApi(); - visitWorkflow('?sort=descending'); - cy.wait('@events-descending-api'); - }); - - it('should sort events in ascending if version history does not support it', () => { - cy.interceptClusterApi('cluster-server-without-reserve-event-sorting.json'); - visitWorkflow(); - cy.wait('@events-ascending-api'); - }); - - it('should sort events in ascending with ascending in query param if version history does not support it', () => { - cy.interceptClusterApi('cluster-server-without-reserve-event-sorting.json'); - visitWorkflow('?sort=ascending'); - cy.wait('@events-ascending-api'); - }); - - it('should sort events in ascending with descending in query param if version history does not support it', () => { - cy.interceptClusterApi('cluster-server-without-reserve-event-sorting.json'); - visitWorkflow('?sort=descending'); - cy.wait('@events-ascending-api'); - }); -}); diff --git a/cypress/integration/events-in-ascending-order-in-json-view.spec.js b/cypress/integration/events-in-ascending-order-in-json-view.spec.js deleted file mode 100644 index d43d4864e..000000000 --- a/cypress/integration/events-in-ascending-order-in-json-view.spec.js +++ /dev/null @@ -1,40 +0,0 @@ -/// - -const visitWorkflow = (suffix = '') => { - cy.visit( - `/namespaces/default/workflows/workflowId/runId/history/json${suffix}`, - ); -}; - -describe('Fallback to Ascending Ordering of Event History on Older Versions of Temporal Server', () => { - beforeEach(() => { - cy.interceptNamespacesApi(); - cy.interceptGithubReleasesApi(); - cy.interceptQueryApi(); - cy.interceptTaskQueuesApi(); - cy.interceptSettingsApi(); - cy.interceptWorkflowApi(); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - '/api/v1/namespaces/default/workflows/workflowId/runs/runId/events?', - { fixture: 'event-history-completed.json' }, - ).as('events-ascending-api'); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - '/api/v1/namespaces/default/workflows/workflowId/runs/runId/events/reverse?', - { fixture: 'event-history-completed-reverse.json' }, - ).as('events-descending-api'); - }); - - it('should show the events in ascending order in the JSON view', () => { - visitWorkflow(); - cy.wait('@events-ascending-api'); - }); - - it('should ignore parameters for sort order in JSON view', () => { - visitWorkflow('?sort=descending'); - cy.wait('@events-ascending-api'); - }); -}); diff --git a/cypress/integration/import.spec.js b/cypress/integration/import.spec.js deleted file mode 100644 index ee8eaed2e..000000000 --- a/cypress/integration/import.spec.js +++ /dev/null @@ -1,7 +0,0 @@ -describe('Redirect to Events Import', () => { - it(`should redirect from "/import" to "/import/events"`, () => { - cy.visit('/import'); - - cy.url().should('include', '/import/events'); - }); -}); diff --git a/cypress/integration/login.spec.js b/cypress/integration/login.spec.js deleted file mode 100644 index 3ad84a845..000000000 --- a/cypress/integration/login.spec.js +++ /dev/null @@ -1,41 +0,0 @@ -describe('Login page', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.intercept(Cypress.env('VITE_API_HOST') + '/api/v1/settings*', { - Auth: { Enabled: true, Options: null }, - DefaultNamespace: 'default', - ShowTemporalSystemNamespace: false, - FeedbackURL: '', - NotifyOnNewVersion: true, - Codec: { Endpoint: '', PassAccessToken: false }, - }).as('settings-api'); - }); - - it('have the correct login title', () => { - cy.visit('/namespaces/default/workflows'); - - cy.wait('@settings-api'); - - cy.url().should('include', `/login`); - - cy.get('[data-cy="navigation-header"]').as('header'); - - cy.get('[data-cy="login-title"]').contains('Welcome back.'); - cy.get('[data-cy="login-info"]').contains(`Let's get you signed in.`); - cy.get('[data-cy="login-button"]').contains('Continue to SSO'); - cy.get('@header') - .find('[data-cy="data-encoder-status-configured"]') - .should('not.exist'); - }); - - it('doesn not redirect to login when user session exists', () => { - cy.login(); - cy.visit('/'); - cy.wait('@settings-api'); - cy.wait('@namespaces-api'); - cy.wait('@workflows-api'); - - cy.url().should('include', `/namespaces/default/workflows`); - }); -}); diff --git a/cypress/integration/namespace.spec.js b/cypress/integration/namespace.spec.js deleted file mode 100644 index 2a7824664..000000000 --- a/cypress/integration/namespace.spec.js +++ /dev/null @@ -1,45 +0,0 @@ -describe('Namespace page', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.visit('/namespaces/default'); - - cy.wait('@namespace-api'); - }); - - it('have the correct namespace information with labels', () => { - cy.get('[data-cy="namespace-title"]').should( - 'to.contain', - 'Namespace: default', - ); - cy.get('[data-cy="namespace-description"]').should( - 'to.contain', - 'Description: Default namespace for Temporal Server.', - ); - cy.get('[data-cy="namespace-owner"]').should( - 'to.contain', - 'Owner: Unknown', - ); - cy.get('[data-cy="namespace-global"]').should('to.contain', 'Global? No'); - cy.get('[data-cy="namespace-retention"]').should( - 'to.contain', - 'Retention Period: 1 day', - ); - cy.get('[data-cy="namespace-history"]').should( - 'to.contain', - 'History Archival: Disabled', - ); - cy.get('[data-cy="namespace-visibility"]').should( - 'to.contain', - 'Visibility Archival: Disabled', - ); - cy.get('[data-cy="namespace-failover"]').should( - 'to.contain', - 'Failover Version: 0', - ); - cy.get('[data-cy="namespace-clusters"]').should( - 'to.contain', - 'Clusters: us-east1 (active), us-east2', - ); - }); -}); diff --git a/cypress/integration/namespaces-archival.spec.js b/cypress/integration/namespaces-archival.spec.js deleted file mode 100644 index 2b49ded1e..000000000 --- a/cypress/integration/namespaces-archival.spec.js +++ /dev/null @@ -1,34 +0,0 @@ -describe('Archival disabled page', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.visit('/namespaces/default/archival'); - - cy.wait('@namespace-api'); - }); - - it('have the correct title on page', () => { - cy.get('[data-cy="archived-disabled-title"]').should( - 'contain', - 'This namespace is currently not enabled for archival.', - ); - }); -}); - -describe('Archival enabled page', () => { - beforeEach(() => { - cy.interceptApi({ archived: true }); - - cy.visit('/namespaces/some-other-namespace/archival'); - - cy.wait('@workflows-archived-api'); - cy.wait('@namespace-api'); - }); - - it('have the correct namespaces in the dropdown', () => { - cy.get('[data-cy="archived-enabled-title"]').should( - 'contain', - 'Archived Workflows', - ); - }); -}); diff --git a/cypress/integration/namespaces.spec.js b/cypress/integration/namespaces.spec.js deleted file mode 100644 index 23b5ad1fb..000000000 --- a/cypress/integration/namespaces.spec.js +++ /dev/null @@ -1,86 +0,0 @@ -const namespaces = ['default', 'some-other-namespace']; - -describe('Namespaces page', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.visit('/namespaces'); - - cy.fixture('namespaces.json') - .then(({ namespaces }) => { - return namespaces - .map((n) => n.namespaceInfo.name) - .filter((name) => name !== 'temporal-system'); - }) - .as('namespaces'); - - cy.wait('@namespaces-api'); - }); - - it('have the correct namespaces in the dropdown when using navigation header', () => { - cy.get('h1').contains('Namespaces'); - cy.get(':nth-child(1) > td').contains(namespaces[0]); - cy.get(':nth-child(2) > td').contains(namespaces[1]); - }); -}); - -describe('Namespaces button', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.visit('/namespaces/default/workflows'); - - cy.fixture('namespaces.json') - .then(({ namespaces }) => { - return namespaces - .map((n) => n.namespaceInfo.name) - .filter((name) => name !== 'temporal-system'); - }) - .as('namespaces'); - - cy.wait('@workflows-api'); - cy.wait('@namespaces-api'); - - cy.get('[data-cy="namespaces-button"]').as('namespaces-button'); - }); - - it('have the correct namespaces in the dropdown when using navigation header', () => { - cy.get('@namespaces-button').click(); - cy.get('[data-cy="namespace-selector-title"]').contains('Namespaces'); - cy.get(':nth-child(1) > td').contains(namespaces[0]); - cy.get(':nth-child(2) > td').contains(namespaces[1]); - }); -}); - -describe('Namespace Select', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.visit('/namespaces/default/workflows'); - - cy.fixture('namespaces.json') - .then(({ namespaces }) => { - return namespaces - .map((n) => n.namespaceInfo.name) - .filter((name) => name !== 'temporal-system'); - }) - .as('namespaces'); - - cy.wait('@workflows-api'); - cy.wait('@namespaces-api'); - - cy.get('[data-cy="namespace-select-button"]').as('namespace-select-button'); - }); - - it('have the correct namespaces in the dropdown when using navigation header', () => { - cy.get('@namespace-select-button').click({ wait: 1000 }); - cy.get('.prose > .text-2xl').contains('Select a namespace'); - }); - - it('navigates to the correct namespaces in the dropdown when using navigation header', () => { - cy.get('@namespace-select-button').click({ wait: 1000 }); - cy.get('.prose > .text-2xl').contains('Select a namespace'); - cy.get('[data-cy="namespace-list"] > :nth-child(2)').click(); - cy.get('[data-cy="namespace-name"]').contains(namespaces[1]); - }); -}); diff --git a/cypress/integration/query.spec.js b/cypress/integration/query.spec.js deleted file mode 100644 index c58ee8ea9..000000000 --- a/cypress/integration/query.spec.js +++ /dev/null @@ -1,53 +0,0 @@ -/// - -import workflowCompletedFixture from '../fixtures/workflow-completed.json'; - -const { workflowId, runId } = - workflowCompletedFixture.workflowExecutionInfo.execution; - -describe('Query', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/*/runs/*/events/reverse*`, - { fixture: 'event-history-completed.json' }, - ).as('event-history-api'); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/${workflowId}/runs/${runId}?`, - { fixture: 'workflow-completed.json' }, - ).as('workflow-api'); - - cy.visit('/'); - cy.wait('@workflows-api'); - cy.wait('@namespaces-api'); - }); - - it('should render Query options for Java SDK', () => { - cy.visit(`/namespaces/default/workflows/${workflowId}/${runId}`); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/*/runs/*/query*`, - { fixture: 'query-java-error-unknown-type.json', statusCode: 400 }, - ).as('query-unknown-type-api'); - - cy.on('uncaught:exception', () => { - return false; - }); - - cy.wait('@workflow-api'); - cy.wait('@event-history-api'); - - cy.get( - `[href="/namespaces/default/workflows/${workflowId}/${runId}/query"]`, - ).click(); - - cy.wait('@query-unknown-type-api'); - - cy.get('[data-cy=query-select]').contains('query1'); - }); -}); diff --git a/cypress/integration/schedules.spec.js b/cypress/integration/schedules.spec.js deleted file mode 100644 index 53b688de5..000000000 --- a/cypress/integration/schedules.spec.js +++ /dev/null @@ -1,99 +0,0 @@ -/// - -import schedulesFixture from '../fixtures/schedules.json'; - -const scheduleFixture = schedulesFixture.schedules[0]; -const { - scheduleId, - info: { - workflowType: { name }, - }, -} = scheduleFixture; - -describe('Schedules List', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.visit('/namespaces/default/schedules'); - - cy.wait('@namespaces-api'); - cy.wait('@schedules-api'); - }); - - it('should show schedule ID and workflow name in first row', () => { - cy.get('.schedule-row').first().contains(scheduleId); - cy.get('.schedule-row').first().contains(name); - }); -}); - -describe('Schedules View', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.visit('/namespaces/default/schedules'); - - cy.wait('@namespaces-api'); - cy.wait('@schedules-api'); - }); - - it('should navigate to a schedule and view the name and frequency', () => { - cy.get('.schedule-row').first().contains(scheduleId); - cy.get('.schedule-row').first().contains(name); - - cy.get('.schedule-row').first().click(); - - cy.wait('@schedule-api'); - - cy.get('[data-cy="schedule-name"]').should('exist'); - cy.get('[data-cy="schedule-name"]').contains(scheduleId); - - cy.get('[data-cy="schedule-interval-frequency"]').contains('Every 30sec'); - }); -}); - -describe('Schedules Edit', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.visit('/namespaces/default/schedules'); - - cy.wait('@namespaces-api'); - cy.wait('@schedules-api'); - }); - - it('should navigate to a schedule and click edit', () => { - cy.get('.schedule-row').first().contains(scheduleId); - cy.get('.schedule-row').first().contains(name); - - cy.get('.schedule-row').first().click(); - - cy.wait('@schedule-api'); - - cy.get('[data-cy="schedule-name"]').should('exist'); - cy.get('[data-cy="schedule-name"]').contains(scheduleId); - - cy.get('[data-cy="schedule-interval-frequency"]').contains('Every 30sec'); - - cy.get('#schedule-actions-menu-button').click(); - cy.get('#schedule-actions-menu > .edit').click(); - cy.url().should('contain', `/schedules/${scheduleId}/edit`); - cy.get('#content').contains('Edit Schedule'); - }); -}); - -describe('Schedules Create', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.visit('/namespaces/default/schedules'); - - cy.wait('@namespaces-api'); - cy.wait('@schedules-api'); - }); - - it('should show Create Schedules Button and navigate to /create', () => { - cy.get('[data-cy="create-schedule"]').click(); - cy.url().should('contain', '/schedules/create'); - cy.get('#content').contains('Create Schedule'); - }); -}); diff --git a/cypress/integration/stack-trace.spec.js b/cypress/integration/stack-trace.spec.js deleted file mode 100644 index af21884aa..000000000 --- a/cypress/integration/stack-trace.spec.js +++ /dev/null @@ -1,93 +0,0 @@ -/// - -import workflowRunningFixture from '../fixtures/workflow-running.json'; -import workflowCompletedFixture from '../fixtures/workflow-completed.json'; - -const { workflowId, runId } = - workflowCompletedFixture.workflowExecutionInfo.execution; - -describe('Stack Trace', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/*/runs/*/events/reverse*`, - { fixture: 'event-history-completed.json' }, - ).as('event-history-api'); - - cy.visit('/namespaces/default/workflows'); - - cy.wait('@workflows-api'); - cy.wait('@namespaces-api'); - }); - - it('should show No Stack Trace for completed workflow', () => { - cy.visit(`/namespaces/default/workflows/${workflowId}/${runId}`); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/${workflowId}/runs/${runId}?`, - { fixture: 'workflow-completed.json' }, - ).as('workflow-api'); - - cy.wait('@workflow-api'); - cy.wait('@event-history-api'); - - cy.get('[data-cy=stack-trace-tab]').click(); - - cy.get('[data-cy="query-stack-trace-empty"]').contains( - 'No Stack Traces Found', - ); - }); - - it('should show stack trace for running workflow', () => { - const { workflowId, runId } = - workflowRunningFixture.workflowExecutionInfo.execution; - - cy.visit(`/namespaces/default/workflows/${workflowId}/${runId}`); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/${workflowId}/runs/${runId}?`, - { fixture: 'workflow-running.json' }, - ).as('workflow-api'); - - cy.wait('@workflow-api'); - cy.wait('@event-history-api'); - - cy.get('[data-cy=stack-trace-tab]').click(); - - cy.wait('@query-api'); - - cy.get('[data-cy="query-stack-trace"]').contains('go.temporal.io/sdk'); - }); - - it('should handle errors when the stack trace is not formatted as we expect', () => { - const { workflowId, runId } = - workflowRunningFixture.workflowExecutionInfo.execution; - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/*/runs/*/query*`, - { fixture: 'query-stack-trace-error.json' }, - ).as('query-api-error'); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/${workflowId}/runs/${runId}?`, - { fixture: 'workflow-running.json' }, - ).as('workflow-api'); - - cy.visit(`/namespaces/default/workflows/${workflowId}/${runId}`); - - cy.wait('@workflow-api'); - cy.wait('@event-history-api'); - - cy.get('[data-cy=stack-trace-tab]').click(); - - cy.wait('@query-api-error'); - - cy.get('[data-cy="query-stack-trace"]').contains('[{"an":"error"}]'); - }); -}); diff --git a/cypress/integration/task-queues.spec.js b/cypress/integration/task-queues.spec.js deleted file mode 100644 index 5883890a9..000000000 --- a/cypress/integration/task-queues.spec.js +++ /dev/null @@ -1,52 +0,0 @@ -/// - -import * as dateTz from 'date-fns-tz'; - -import wtq from '../fixtures/worker-task-queues.json'; -import atq from '../fixtures/activity-task-queues.json'; - -describe('Task Queues Page', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.visit(`/namespaces/default/task-queues/a-task-queue`); - - cy.wait('@namespaces-api'); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/task-queues/a-task-queue?taskQueueType=1`, - { - fixture: 'worker-task-queues.json', - }, - ).as('worker-task-queues-api'); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/task-queues/a-task-queue?taskQueueType=2`, - { - fixture: 'activity-task-queues.json', - }, - ).as('activity-task-queues-api'); - }); - - it('View a task queue page with a worker', () => { - cy.wait('@worker-task-queues-api'); - cy.wait('@activity-task-queues-api'); - - cy.get('[data-cy="pollers-title"]').contains('Pollers'); - cy.get('.text-lg').contains('Task Queue: a-task-queue'); - cy.get('[data-cy=worker-row]').should('have.length', 1); - cy.get('[data-cy=worker-identity]').contains(wtq.pollers[0].identity); - cy.get('[data-cy=worker-last-access-time]').contains( - dateTz.formatInTimeZone( - new Date(atq.pollers[0].lastAccessTime), - 'UTC', - 'yyyy-MM-dd z HH:mm:ss.SS', - ), - ); - - cy.get('[data-cy="workflow-poller"] > .text-blue-700').should('exist'); - cy.get('[data-cy="activity-poller"] > .text-blue-700').should('exist'); - }); -}); diff --git a/cypress/integration/workers.spec.js b/cypress/integration/workers.spec.js deleted file mode 100644 index fa1c2f36d..000000000 --- a/cypress/integration/workers.spec.js +++ /dev/null @@ -1,170 +0,0 @@ -/// - -import * as dateTz from 'date-fns-tz'; - -import workflowCompletedFixture from '../fixtures/workflow-completed.json'; -import wtq from '../fixtures/worker-task-queues.json'; -import atq from '../fixtures/activity-task-queues.json'; - -const { workflowId, runId } = - workflowCompletedFixture.workflowExecutionInfo.execution; -const { name } = workflowCompletedFixture.executionConfig.taskQueue; - -describe('Workflow Workers', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/${workflowId}/runs/${runId}?`, - { fixture: 'workflow-completed.json' }, - ).as('workflow-api'); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/task-queues/${name}?taskQueueType=1`, - { - fixture: 'worker-task-queues.json', - }, - ).as('worker-task-queues-api'); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/task-queues/${name}?taskQueueType=2`, - { - fixture: 'activity-task-queues.json', - }, - ).as('activity-task-queues-api'); - - cy.visit(`/namespaces/default/workflows/${workflowId}/${runId}/workers`); - - cy.wait('@namespaces-api'); - cy.wait('@workflow-api'); - }); - - it('View both worker and activity poller', () => { - cy.get('[data-cy=workers-tab]').click(); - - cy.wait('@worker-task-queues-api'); - cy.wait('@activity-task-queues-api'); - - cy.get('[data-cy=worker-row]').should('have.length', 1); - cy.get('[data-cy=worker-identity]').contains(wtq.pollers[0].identity); - cy.get('[data-cy=worker-last-access-time]').contains( - dateTz.formatInTimeZone( - new Date(atq.pollers[0].lastAccessTime), - 'UTC', - 'yyyy-MM-dd z HH:mm:ss.SS', - ), - ); - - cy.get('[data-cy="workflow-poller"] > .text-blue-700').should('exist'); - cy.get('[data-cy="activity-poller"] > .text-blue-700').should('exist'); - }); -}); - -describe('Workflow Workers - Workflow Worker Only', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/${workflowId}/runs/${runId}?`, - { fixture: 'workflow-completed.json' }, - ).as('workflow-api'); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/task-queues/${name}?taskQueueType=1`, - { - fixture: 'worker-task-queues.json', - }, - ).as('worker-task-queues-api'); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/task-queues/${name}?taskQueueType=2`, - { - fixture: 'empty-task-queues.json', - }, - ).as('activity-task-queues-api'); - - cy.visit(`/namespaces/default/workflows/${workflowId}/${runId}/workers`); - - cy.wait('@namespaces-api'); - cy.wait('@workflow-api'); - }); - - it('View workflow worker only poller', () => { - cy.get('[data-cy=workers-tab]').click(); - - cy.wait('@worker-task-queues-api'); - cy.wait('@activity-task-queues-api'); - - cy.get('[data-cy=worker-row]').should('have.length', 1); - cy.get('[data-cy=worker-identity]').contains(wtq.pollers[0].identity); - cy.get('[data-cy=worker-last-access-time]').contains( - dateTz.formatInTimeZone( - new Date(wtq.pollers[0].lastAccessTime), - 'UTC', - 'yyyy-MM-dd z HH:mm:ss.SS', - ), - ); - - cy.get('[data-cy="workflow-poller"] > .text-blue-700').should('exist'); - cy.get('[data-cy="activity-poller"] > .text-blue-700').should('not.exist'); - }); -}); - -describe('Workflow Workers - Activity Worker Only', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/${workflowId}/runs/${runId}?`, - { fixture: 'workflow-completed.json' }, - ).as('workflow-api'); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/task-queues/${name}?taskQueueType=1`, - { - fixture: 'empty-task-queues.json', - }, - ).as('worker-task-queues-api'); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/task-queues/${name}?taskQueueType=2`, - { - fixture: 'activity-task-queues.json', - }, - ).as('activity-task-queues-api'); - - cy.visit(`/namespaces/default/workflows/${workflowId}/${runId}/workers`); - - cy.wait('@namespaces-api'); - cy.wait('@workflow-api'); - }); - - it('View activity worker only poller', () => { - cy.get('[data-cy=workers-tab]').click(); - - cy.wait('@worker-task-queues-api'); - cy.wait('@activity-task-queues-api'); - - cy.get('[data-cy=worker-row]').should('have.length', 1); - cy.get('[data-cy=worker-identity]').contains(atq.pollers[0].identity); - cy.get('[data-cy=worker-last-access-time]').contains( - dateTz.formatInTimeZone( - new Date(atq.pollers[0].lastAccessTime), - 'UTC', - 'yyyy-MM-dd z HH:mm:ss.SS', - ), - ); - - cy.get('[data-cy="workflow-poller"] > .text-blue-700').should('not.exist'); - cy.get('[data-cy="activity-poller"] > .text-blue-700').should('exist'); - }); -}); diff --git a/cypress/integration/workflow-actions.spec.js b/cypress/integration/workflow-actions.spec.js deleted file mode 100644 index db25893fe..000000000 --- a/cypress/integration/workflow-actions.spec.js +++ /dev/null @@ -1,75 +0,0 @@ -/// - -import workflowCompletedFixture from '../fixtures/workflow-running.json'; - -const { workflowId, runId } = - workflowCompletedFixture.workflowExecutionInfo.execution; - -describe('Workflow Actions', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/*/runs/*/events/*`, - { fixture: 'event-history-completed-reverse.json' }, - ).as('event-history-api'); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/${workflowId}/runs/${runId}?`, - { fixture: 'workflow-running.json' }, - ).as('workflow-api'); - }); - - describe('Terminate', () => { - it('works if the workflow is running and write actions are enabled', () => { - cy.visit( - `/namespaces/default/workflows/${workflowId}/${runId}/history/feed?sort=descending`, - ); - - cy.wait('@settings-api'); - cy.wait('@workflow-api'); - - cy.get('#workflow-actions-menu-button').click(); - cy.get('#workflow-actions-menu > [data-cy="terminate-button"]').click(); - cy.get('#workflow-termination-reason').type('test'); - cy.get('[data-cy="confirm-modal-button"').click(); - cy.get('#workflow-termination-success-toast').should('exist'); - }); - }); - - describe('Cancel', () => { - it('works if the workflow is running a write actions are enabled', () => { - cy.visit( - `/namespaces/default/workflows/${workflowId}/${runId}/history/feed?sort=descending`, - ); - - cy.wait('@settings-api'); - cy.wait('@workflow-api'); - - cy.get('#workflow-actions-primary-button').click(); - cy.get('[data-cy="confirm-modal-button"]').click(); - - cy.wait('@cancel-workflow-api'); - }); - }); - - describe('Write Actions Disabled', () => { - it('the Cancel button is disabled if write actions are disabled', () => { - cy.intercept(Cypress.env('VITE_API_HOST') + `/api/v1/settings?`, { - fixture: 'settings.write-actions-disabled.json', - }).as('settings-api'); - - cy.visit( - `/namespaces/default/workflows/${workflowId}/${runId}/history/feed?sort=descending`, - ); - - cy.wait('@settings-api'); - cy.wait('@workflow-api'); - - cy.get('#workflow-actions-primary-button').should('be.disabled'); - cy.get('#workflow-actions-menu-button').should('be.disabled'); - }); - }); -}); diff --git a/cypress/integration/workflow-bulk-actions.spec.js b/cypress/integration/workflow-bulk-actions.spec.js deleted file mode 100644 index 48adbe5a8..000000000 --- a/cypress/integration/workflow-bulk-actions.spec.js +++ /dev/null @@ -1,34 +0,0 @@ -/// - -describe('Bulk Termination', () => { - it("disallows bulk actions for cluster that doesn't have elasticsearch enabled", () => { - cy.interceptApi(); - - cy.visit('/namespaces/default/workflows'); - - cy.wait('@workflows-api'); - cy.wait('@cluster-api'); - - cy.get('#workflows-table-with-bulk-actions').should('not.exist'); - }); - - it('allows running workflows to be terminated for cluster that does have elasticsearch enabled', () => { - cy.interceptApi(); - cy.intercept(Cypress.env('VITE_API_HOST') + '/api/v1/cluster*', { - fixture: 'cluster-with-elasticsearch.json', - }).as('cluster-api-elasticsearch'); - - cy.visit('/namespaces/default/workflows'); - - cy.wait('@workflows-api'); - cy.wait('@cluster-api-elasticsearch'); - - cy.get('#workflows-table-with-bulk-actions').should('exist'); - - cy.get('th.selectable > label.checkbox > span.label').click(); - cy.get('[data-cy="bulk-terminate-button"]').click(); - cy.get('#bulk-terminate-reason').type('Sarah Connor'); - cy.get('div.modal button.destructive').click(); - cy.get('#batch-terminate-success-toast'); - }); -}); diff --git a/cypress/integration/workflow-executions-with-new-search.spec.js b/cypress/integration/workflow-executions-with-new-search.spec.js deleted file mode 100644 index 8991f9c21..000000000 --- a/cypress/integration/workflow-executions-with-new-search.spec.js +++ /dev/null @@ -1,225 +0,0 @@ -/// - -import workflowsFixture from '../fixtures/workflows.json'; - -const workflowRunningFixture = workflowsFixture.executions[0]; -const { workflowId, runId } = workflowRunningFixture.execution; - -describe.skip('Workflow Executions List With Search', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.visit('/namespaces/default/workflows'); - - cy.wait('@workflows-api'); - cy.wait('@workflows-count-api'); - cy.wait('@namespaces-api'); - }); - - it('should show count of workflows', () => { - cy.get('[data-cy="workflow-count"]').should('contain', '15 workflows'); - }); - - it('should default to All for the time range', () => { - cy.get('#time-range-filter').should('have.value', ''); - }); - - describe('Workflow Manual Search', () => { - it('should change url on manual search and update filters and show results count', () => { - cy.get('#manual-search').type('WorkflowType="ImportantWorkflowType"'); - cy.get('[data-cy="manual-search-button"]').click(); - - const result = encodeURIComponent('WorkflowType="ImportantWorkflowType"'); - cy.url().should('contain', result); - - cy.get('[data-cy="workflow-type-filter-button"]').click(); - cy.get('#workflowType').should('have.value', 'ImportantWorkflowType'); - - cy.get('[data-cy="workflow-count"]').should( - 'contain', - 'Results 15 of 15 workflows', - ); - }); - }); - - describe('Workflow Filters', () => { - it('should send the correct query for Workflow Type, autocomplete manual search and be clearable', () => { - cy.get('[data-cy="workflow-type-filter-button"]').click(); - - cy.get('#workflowType').type('ImportantWorkflowType'); - - const result = encodeURIComponent('WorkflowType="ImportantWorkflowType"'); - cy.url().should('contain', result); - cy.get('#manual-search').should( - 'have.value', - 'WorkflowType="ImportantWorkflowType"', - ); - - cy.get('[data-cy="workflow-count"]').should( - 'contain', - 'Results 15 of 15 workflows', - ); - - cy.get( - '.px-2 > .input-container > [data-cy="clear-input"] > svg', - ).click(); - - cy.url().should('not.contain', result); - cy.get('[data-cy="workflow-count"]').should('contain', '15 workflows'); - }); - - it('should send the correct query for Workflow ID, autocomplete manual search and be clearable', () => { - cy.get('[data-cy="workflow-id-filter-button"]').click(); - - cy.get('#workflowId').type('002c98_Running'); - - const result = encodeURIComponent('WorkflowId="002c98_Running"'); - cy.url().should('contain', result); - cy.get('#manual-search').should( - 'have.value', - 'WorkflowId="002c98_Running"', - ); - cy.get('[data-cy="workflow-count"]').should( - 'contain', - 'Results 15 of 15 workflows', - ); - - cy.get( - '.px-2 > .input-container > [data-cy="clear-input"] > svg', - ).click(); - - cy.url().should('not.contain', result); - cy.get('[data-cy="workflow-count"]').should('contain', '15 workflows'); - }); - - it('should change url on single Execution Status change', () => { - cy.get('[data-cy="execution-status-filter-button"]').click(); - cy.get('[data-cy="Running"]').click(); - - const result = encodeURIComponent('ExecutionStatus="Running"'); - cy.url().should('contain', result); - cy.get('#manual-search').should( - 'have.value', - 'ExecutionStatus="Running"', - ); - cy.get('[data-cy="workflow-count"]').should( - 'contain', - 'Results 15 of 15 workflows', - ); - }); - - it('should change url on multiple Execution Status change', () => { - cy.get('[data-cy="execution-status-filter-button"]').click(); - cy.get('[data-cy="Running"]').click(); - cy.get('[data-cy="Failed"]').click(); - - const result = - '%28ExecutionStatus%3D%22Running%22+OR+ExecutionStatus%3D%22Failed%22%29'; - cy.url().should('contain', result); - cy.get('#manual-search').should( - 'have.value', - '(ExecutionStatus="Running" OR ExecutionStatus="Failed")', - ); - }); - - it('should clear Execution Status on All', () => { - cy.get('[data-cy="execution-status-filter-button"]').click(); - cy.get('[data-cy="Running"]').click(); - cy.get('[data-cy="Failed"]').click(); - - const result = - '%28ExecutionStatus%3D%22Running%22+OR+ExecutionStatus%3D%22Failed%22%29'; - cy.url().should('contain', result); - - cy.get('[data-cy="All"]').click(); - cy.url().should('contain.not', result); - }); - - it('should combine all three filters', () => { - cy.get('[data-cy="execution-status-filter-button"]').click(); - cy.get('[data-cy="Running"]').click(); - - cy.get('[data-cy="workflow-id-filter-button"]').click(); - cy.get('#workflowId').type('002c98_Running'); - - cy.get('[data-cy="workflow-type-filter-button"]').click(); - cy.get('#workflowType').type('ImportantWorkflowType'); - - const result = - 'ExecutionStatus%3D%22Running%22+AND+WorkflowId%3D%22002c98_Running%22+AND+WorkflowType%3D%22ImportantWorkflowType%22'; - cy.url().should('contain', result); - cy.get('#manual-search').should( - 'have.value', - 'ExecutionStatus="Running" AND WorkflowId="002c98_Running" AND WorkflowType="ImportantWorkflowType"', - ); - }); - - it('should send the correct query for StartTime', () => { - cy.get('#time-range-filter').click(); - cy.contains('15 minutes').click(); - - cy.url().should('contain', 'StartTime+BETWEEN'); - cy.get('[data-cy="workflow-count"]').should( - 'contain', - 'Results 15 of 15 workflows', - ); - }); - - it('should send the correct query for CloseTime', () => { - cy.get('#time-range-filter').click(); - cy.contains('3 hours').click(); - cy.contains('End Time').click(); - - cy.url().should('contain', 'CloseTime+BETWEEN'); - cy.get('[data-cy="workflow-count"]').should( - 'contain', - 'Results 15 of 15 workflows', - ); - }); - - describe('Workflow Filters with Navigation ', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/*/runs/*/events/reverse*`, - { fixture: 'event-history-completed.json' }, - ).as('event-history-api'); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/${workflowId}/runs/${runId}?`, - { fixture: 'workflow-completed.json' }, - ).as('workflow-api'); - }); - - it('should keep single workflow filter after navigating away and back to workflow list', () => { - cy.get('[data-cy="execution-status-filter-button"]').click(); - cy.get('[data-cy="Running"]').click(); - - cy.url().should( - 'contain', - encodeURIComponent(`ExecutionStatus="Running"`), - ); - - cy.get('.workflow-summary-row').first().click(); - - cy.wait('@workflow-api'); - cy.wait('@event-history-api'); - - cy.url().should('contain', '/feed'); - cy.get('[data-cy="back-to-workflows"]').click(); - - cy.url().should( - 'contain', - encodeURIComponent(`ExecutionStatus="Running"`), - ); - cy.get('[data-cy="workflow-count"]').should( - 'contain', - 'Results 15 of 15 workflows', - ); - }); - }); - }); -}); diff --git a/cypress/integration/workflow-executions.spec.js b/cypress/integration/workflow-executions.spec.js deleted file mode 100644 index f993c9173..000000000 --- a/cypress/integration/workflow-executions.spec.js +++ /dev/null @@ -1,126 +0,0 @@ -/// - -import workflowsFixture from '../fixtures/workflows.json'; - -const workflowRunningFixture = workflowsFixture.executions[0]; -const { workflowId, runId } = workflowRunningFixture.execution; - -const statuses = [ - 'Running', - 'TimedOut', - 'Completed', - 'Failed', - 'ContinuedAsNew', - 'Canceled', - 'Terminated', -]; - -describe('Workflow Executions List', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.visit('/namespaces/default/workflows'); - - cy.wait('@workflows-api'); - cy.wait('@namespaces-api'); - }); - - it('should default to All for the time range', () => { - cy.get('#time-range-filter').find('option').should('have.value', 'null'); - }); - - it('should default to showing all workflows', () => { - cy.get('#execution-status-filter') - .find('option:selected') - .should('have.value', 'null'); - - cy.get('#workflow-id-filter').should('have.value', ''); - - cy.get('#workflow-type-filter').should('have.value', ''); - }); - - describe('Workflow Filters', () => { - it('should send the correct query for Workflow Type', () => { - const result = encodeURIComponent('WorkflowType="ImportantWorkflowType"'); - - cy.get('#workflow-type-filter').type('ImportantWorkflowType'); - - cy.url().should('contain', result); - }); - - it('should send the correct query for Workflow ID', () => { - const result = encodeURIComponent('WorkflowId="002c98_Running"'); - - cy.get('#workflow-id-filter').type('002c98_Running'); - - cy.url().should('contain', result); - }); - - for (const status of statuses) { - it(`should redirect to the correct query params for ${status} workflows`, () => { - cy.visit(`/namespaces/default/workflows`); - cy.get('#execution-status-filter').select(status).trigger('input'); - cy.url().should( - 'contain', - encodeURIComponent(`ExecutionStatus="${status}"`), - ); - }); - - it(`should send the correct query when filtering for ${status} workflows`, () => { - cy.visit( - `/namespaces/default/workflows?query=${encodeURIComponent( - `ExecutionStatus="${status}"`, - )}`, - ); - - cy.get('#execution-status-filter').should('have.value', status); - }); - } - - describe('Workflow Filters with Navigation ', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/*/runs/*/events/reverse*`, - { fixture: 'event-history-completed.json' }, - ).as('event-history-api'); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/${workflowId}/runs/${runId}?`, - { fixture: 'workflow-completed.json' }, - ).as('workflow-api'); - }); - - it('should keep single workflow filter after navigating away and back to workflow list', () => { - cy.get('#execution-status-filter') - .find('option:selected') - .should('have.value', 'null'); - - cy.get('#execution-status-filter').select('Running').trigger('input'); - cy.url().should( - 'contain', - encodeURIComponent(`ExecutionStatus="Running"`), - ); - - cy.get('.workflow-summary-row').first().click(); - - cy.wait('@workflow-api'); - cy.wait('@event-history-api'); - - cy.url().should('contain', '/feed'); - cy.get('[data-cy="back-to-workflows"]').click(); - - cy.url().should( - 'contain', - encodeURIComponent(`ExecutionStatus="Running"`), - ); - cy.get('#execution-status-filter') - .find('option:selected') - .should('have.value', 'Running'); - }); - }); - }); -}); diff --git a/cypress/integration/workflow-input-and-results.spec.js b/cypress/integration/workflow-input-and-results.spec.js deleted file mode 100644 index 3aaa3861c..000000000 --- a/cypress/integration/workflow-input-and-results.spec.js +++ /dev/null @@ -1,222 +0,0 @@ -/// - -import workflowsFixture from '../fixtures/workflows.json'; -import eventsCompletedFixture from '../fixtures/event-history-completed.json'; -import eventsCompletedNullFixture from '../fixtures/event-history-completed-null.json'; -import eventsRunningFixture from '../fixtures/event-history-running.json'; -import eventsFailedFixture from '../fixtures/event-history-failed.json'; -import eventsCanceledFixture from '../fixtures/event-history-canceled.json'; -import eventsContinuedAsNewFixture from '../fixtures/event-history-continued-as-new.json'; -import eventsTimedOutFixture from '../fixtures/event-history-timed-out.json'; - -const workflow = workflowsFixture.executions[0]; -const { workflowId, runId } = workflow.execution; - -describe('Workflow Input and Results', () => { - beforeEach(() => { - cy.interceptApi(); - - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/${workflowId}/runs/${runId}?`, - { fixture: 'workflow-completed.json' }, - ).as('workflow-api'); - - cy.visit('/namespaces/default/workflows'); - - cy.wait('@workflows-api'); - cy.wait('@namespaces-api'); - }); - - it('should show the input and result for completed workflow', () => { - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/*/runs/*/events/reverse?`, - { fixture: 'event-history-completed.json' }, - ).as('event-history-api'); - - cy.visit(`/namespaces/default/workflows/${workflowId}/${runId}`); - - cy.wait('@event-history-api'); - - cy.get('.accordion-open').click(); - - const firstEvent = eventsCompletedFixture.history.events[0]; - const input = Buffer.from( - firstEvent.workflowExecutionStartedEventAttributes.input.payloads[0].data, - 'base64', - ).toString(); - cy.get('[data-cy="workflow-input"]').contains(input); - - const lastEvent = - eventsCompletedFixture.history.events[ - eventsCompletedFixture.history.events.length - 1 - ]; - const results = Buffer.from( - lastEvent.workflowExecutionCompletedEventAttributes.result.payloads[0] - .data, - 'base64', - ).toString(); - - cy.get('[data-cy="workflow-results"]').contains(results); - }); - - it('should show the input and result for completed workflow and null result', () => { - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/*/runs/*/events/reverse?`, - { fixture: 'event-history-completed-null.json' }, - ).as('event-history-api'); - - cy.visit(`/namespaces/default/workflows/${workflowId}/${runId}`); - - cy.wait('@workflow-api'); - cy.wait('@event-history-api'); - - cy.get('.accordion-open').click(); - - const firstEvent = eventsCompletedNullFixture.history.events[0]; - const input = Buffer.from( - firstEvent.workflowExecutionStartedEventAttributes.input.payloads[0].data, - 'base64', - ).toString(); - cy.get('[data-cy="workflow-input"]').contains(input); - - const lastEvent = - eventsCompletedNullFixture.history.events[ - eventsCompletedNullFixture.history.events.length - 1 - ]; - const results = String( - lastEvent.workflowExecutionCompletedEventAttributes.result, - ); - cy.get('[data-cy="workflow-results"]').contains(results); - }); - - it('should show the input and result for running workflow', () => { - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/*/runs/*/events/reverse?`, - { fixture: 'event-history-running.json' }, - ).as('event-history-api'); - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/${workflowId}/runs/${runId}?`, - { fixture: 'workflow-running.json' }, - ).as('workflow-api'); - - cy.visit(`/namespaces/default/workflows/${workflowId}/${runId}`); - - cy.wait('@workflow-api'); - cy.wait('@event-history-api'); - - cy.get('.accordion-open').click(); - - const firstEvent = eventsRunningFixture.history.events[0]; - const input = Buffer.from( - firstEvent.workflowExecutionStartedEventAttributes.input.payloads[0].data, - 'base64', - ).toString(); - cy.get('[data-cy="workflow-input"]').contains(input); - cy.get('[data-cy="workflow-results"]').contains('In progress'); - }); - - it('should show the input and results for failed workflow', () => { - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/*/runs/*/events/reverse?`, - { fixture: 'event-history-failed.json' }, - ).as('event-history-api'); - - cy.visit(`/namespaces/default/workflows/${workflowId}/${runId}`); - - cy.wait('@workflow-api'); - cy.wait('@event-history-api'); - - cy.get('.accordion-open').click(); - - const firstEvent = eventsFailedFixture.history.events[0]; - const input = Buffer.from( - firstEvent.workflowExecutionStartedEventAttributes.input.payloads[0].data, - 'base64', - ).toString(); - cy.get('[data-cy="workflow-input"]').contains(input); - - const lastEvent = - eventsFailedFixture.history.events[ - eventsFailedFixture.history.events.length - 1 - ]; - const results = String( - lastEvent.workflowExecutionFailedEventAttributes.failure.cause.message, - ); - cy.get('[data-cy="workflow-results"]').contains(results); - }); - - it('should show the input and results for cancelled workflow', () => { - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/*/runs/*/events/reverse?`, - { fixture: 'event-history-canceled.json' }, - ).as('event-history-api'); - - cy.visit(`/namespaces/default/workflows/${workflowId}/${runId}`); - - cy.wait('@workflow-api'); - cy.wait('@event-history-api'); - - cy.get('.accordion-open').click(); - - const firstEvent = eventsCanceledFixture.history.events[0]; - const input = Buffer.from( - firstEvent.workflowExecutionStartedEventAttributes.input.payloads[0].data, - 'base64', - ).toString(); - cy.get('[data-cy="workflow-input"]').contains(input); - cy.get('[data-cy="workflow-results"]').contains('Canceled'); - }); - - it('should show the input and results for timed out workflow', () => { - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/*/runs/*/events/reverse?`, - { fixture: 'event-history-timed-out.json' }, - ).as('event-history-api'); - - cy.visit(`/namespaces/default/workflows/${workflowId}/${runId}`); - - cy.wait('@workflow-api'); - cy.wait('@event-history-api'); - - cy.get('.accordion-open').click(); - - const firstEvent = eventsTimedOutFixture.history.events[0]; - const input = Buffer.from( - firstEvent.workflowExecutionStartedEventAttributes.input.payloads[0].data, - 'base64', - ).toString(); - cy.get('[data-cy="workflow-input"]').contains(input); - cy.get('[data-cy="workflow-results"]').contains('Timeout'); - }); - - it('should show the input and results for continued as new workflow', () => { - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/default/workflows/*/runs/*/events/reverse?`, - { fixture: 'event-history-continued-as-new.json' }, - ).as('event-history-api'); - - cy.visit(`/namespaces/default/workflows/${workflowId}/${runId}`); - - cy.wait('@workflow-api'); - cy.wait('@event-history-api'); - - cy.get('.accordion-open').click(); - - const firstEvent = eventsContinuedAsNewFixture.history.events[0]; - const input = Buffer.from( - firstEvent.workflowExecutionStartedEventAttributes.input.payloads[0].data, - 'base64', - ).toString(); - cy.get('[data-cy="workflow-input"]').contains(input); - cy.get('[data-cy="workflow-results"]').contains('ContinuedAsNew'); - }); -}); diff --git a/cypress/plugins/index.cjs b/cypress/plugins/index.cjs deleted file mode 100644 index 8ede70218..000000000 --- a/cypress/plugins/index.cjs +++ /dev/null @@ -1,22 +0,0 @@ -/// -// *********************************************************** -// This example plugins/index.js can be used to load plugins -// -// You can change the location of this file or turn off loading -// the plugins file with the 'pluginsFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/plugins-guide -// *********************************************************** - -// This function is called when a project is opened or re-opened (e.g. due to -// the project's config changing) - -/** - * @type {Cypress.PluginConfig} - */ -module.exports = (_on, config) => { - // `on` is used to hook into various events Cypress emits - // `config` is the resolved Cypress config - return config; -}; diff --git a/cypress/support/commands.js b/cypress/support/commands.js deleted file mode 100644 index 82aadd04e..000000000 --- a/cypress/support/commands.js +++ /dev/null @@ -1,189 +0,0 @@ -// *********************************************** -// This example commands.js shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// -// -// -- This is a parent command -- -// Cypress.Commands.add('login', (email, password) => { ... }) -// -// -// -- This is a child command -- -// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) - -import settings from '../fixtures/settings.json'; -import user from '../fixtures/user.json'; - -Cypress.Commands.add('interceptNamespacesApi', () => { - cy.intercept(Cypress.env('VITE_API_HOST') + '/api/v1/namespaces*', { - fixture: 'namespaces.json', - }).as('namespaces-api'); -}); - -Cypress.Commands.add('interceptNamespaceApi', ({ archived }) => { - cy.intercept(Cypress.env('VITE_API_HOST') + '/api/v1/namespaces/*', { - fixture: archived ? 'archived-namespace.json' : 'namespace.json', - }).as('namespace-api'); -}); - -Cypress.Commands.add('interceptSearchAttributesApi', () => { - cy.intercept(Cypress.env('VITE_API_HOST') + '/api/v1/search-attributes*', { - fixture: 'search-attributes.json', - }).as('search-attributes-api'); -}); - -Cypress.Commands.add('interceptWorkflowsApi', () => { - cy.intercept( - Cypress.env('VITE_API_HOST') + `/api/v1/namespaces/*/workflows?query=*`, - { fixture: 'workflows.json' }, - ).as('workflows-api'); -}); - -Cypress.Commands.add('interceptWorkflowsCountApi', () => { - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/*/workflows/count?query=*`, - { fixture: 'count.json' }, - ).as('workflows-count-api'); -}); - -Cypress.Commands.add( - 'interceptWorkflowApi', - (fixture = 'workflow-completed.json') => { - cy.intercept( - Cypress.env('VITE_API_HOST') + '/api/v1/namespaces/*/workflows/*/runs/*?', - { fixture }, - ).as('workflow-api'); - }, -); - -Cypress.Commands.add('login', () => { - window.localStorage.setItem('AuthUser', JSON.stringify(user)); -}); - -Cypress.Commands.add('interceptClusterApi', (fixture = 'cluster.json') => { - cy.intercept(Cypress.env('VITE_API_HOST') + '/api/v1/cluster*', { - fixture, - }).as('cluster-api'); -}); - -Cypress.Commands.add('interceptArchivedWorkflowsApi', () => { - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/*/workflows/archived?query=*`, - { fixture: 'workflows.json' }, - ).as('workflows-archived-api'); -}); - -Cypress.Commands.add('interceptSettingsApi', (namespace = 'default') => { - cy.intercept(Cypress.env('VITE_API_HOST') + '/api/v1/settings*', { - ...settings, - DefaultNamespace: namespace, - }).as('settings-api'); -}); - -Cypress.Commands.add('interceptGithubReleasesApi', () => { - cy.intercept('https://api.github.com/repos/temporalio/ui-server/releases', { - fixture: 'github-releases.json', - }).as('github-releases-api'); -}); - -Cypress.Commands.add('interceptQueryApi', () => { - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/*/workflows/*/runs/*/query*`, - { fixture: 'query-stack-trace.json' }, - ).as('query-api'); -}); - -Cypress.Commands.add('interceptTaskQueuesApi', () => { - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/*/task-queues/*?taskQueueType=*`, - { - fixture: 'worker-task-queues.json', - }, - ).as('task-queues-api'); -}); - -Cypress.Commands.add('interceptSchedulesApi', () => { - cy.intercept( - Cypress.env('VITE_API_HOST') + `/api/v1/namespaces/*/schedules*`, - { fixture: 'schedules.json' }, - ).as('schedules-api'); -}); - -Cypress.Commands.add('interceptScheduleApi', () => { - cy.intercept( - Cypress.env('VITE_API_HOST') + `/api/v1/namespaces/*/schedules/*`, - { fixture: 'schedule.json' }, - ).as('schedule-api'); -}); - -Cypress.Commands.add('interceptBatchTerminateApi', () => { - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/*/workflows/batch/terminate?`, - { statusCode: 200, body: {} }, - ).as('batch-terminate-api'); -}); - -Cypress.Commands.add('interceptDescribeBatchOperationApi', () => { - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/*/workflows/batch/describe?jobId=*`, - { fixture: 'batch-operation-status.json' }, - ).as('describe-batch-operation-api'); -}); - -Cypress.Commands.add('interceptTerminateWorkflowApi', () => { - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/*/workflows/*/runs/*/terminate?`, - { statusCode: 200, body: {} }, - ).as('terminate-workflow-api'); -}); - -Cypress.Commands.add('interceptCancelWorkflowApi', () => { - cy.intercept( - Cypress.env('VITE_API_HOST') + - `/api/v1/namespaces/*/workflows/*/runs/*/cancel?`, - { statusCode: 200, body: {} }, - ).as('cancel-workflow-api'); -}); - -Cypress.Commands.add( - 'interceptApi', - ({ namespace, archived } = { namespace: 'default', archived: false }) => { - cy.interceptNamespacesApi(); - cy.interceptNamespaceApi({ archived }); - cy.interceptWorkflowsApi(); - cy.interceptWorkflowsCountApi(); - cy.interceptClusterApi(); - cy.interceptArchivedWorkflowsApi(); - cy.interceptGithubReleasesApi(); - cy.interceptQueryApi(); - cy.interceptTaskQueuesApi(); - cy.interceptSettingsApi(); - cy.interceptSearchAttributesApi(); - cy.interceptSchedulesApi(); - cy.interceptScheduleApi(); - cy.interceptBatchTerminateApi(); - cy.interceptDescribeBatchOperationApi(); - cy.interceptTerminateWorkflowApi(); - cy.interceptCancelWorkflowApi(); - }, -); diff --git a/cypress/support/index.js b/cypress/support/index.js deleted file mode 100644 index 37a498fb5..000000000 --- a/cypress/support/index.js +++ /dev/null @@ -1,20 +0,0 @@ -// *********************************************************** -// This example support/index.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** - -// Import commands.js using ES2015 syntax: -import './commands'; - -// Alternatively you can use CommonJS syntax: -// require('./commands') diff --git a/package.json b/package.json index c0ddc86a7..3695cd5e8 100644 --- a/package.json +++ b/package.json @@ -1,127 +1,234 @@ { "name": "@temporalio/ui", - "version": "2.1.86", + "version": "2.39.0", + "engines": { + "pnpm": ">=8.6.0", + "node": ">=18.15.0" + }, "type": "module", "description": "Temporal.io UI", "keywords": [ "temporal", - "ui" + "ui", + "" + ], + "exports": { + "./*.svelte": { + "types": "./dist/*.svelte.d.ts", + "svelte": "./dist/*.svelte" + }, + "./*": { + "types": "./dist/*.d.ts", + "import": "./dist/*.js" + } + }, + "files": [ + "dist/**/*", + "!dist/**/*.test.*", + "!dist/**/*.spec.*" ], "license": "MIT", "homepage": "https://github.com/temporalio/ui/tree/main", "scripts": { - "start": "pnpm run dev:local -- --open", - "dev:local": ". ./.env && VITE_TEMPORAL_UI_BUILD_TARGET=local vite dev --port 3000", - "dev:cloud": "VITE_TEMPORAL_UI_BUILD_TARGET=cloud vite dev --port 3000", - "dev:test": ". ./.env && VITE_TEMPORAL_UI_BUILD_TARGET=local VITE_MODE=test vite dev --port 3000", + "prepare": "svelte-kit sync && esno scripts/download-temporal.ts && husky install", + "eslint": "eslint --ignore-path .gitignore .", + "eslint:fix": "eslint --ignore-path .gitignore --fix .", + "dev": "pnpm dev:ui-server -- --open", + "dev:ui-server": ". ./.env.ui-server && vite dev --mode ui-server", + "dev:local-temporal": ". ./.env.local-temporal && vite dev --mode local-temporal", + "dev:temporal-cli": "vite dev --mode temporal-server", + "dev:docker": ". ./.env && VITE_API=http://localhost:8080 vite dev --mode docker", "build:local": "vite build", - "build:cloud": "VITE_TEMPORAL_UI_BUILD_TARGET=cloud vite build", + "build:docker": "VITE_API=http://localhost:8080 vite build", + "build:server": "VITE_API= BUILD_PATH=server/ui/assets/local vite build", + "temporal-server": "esno scripts/start-temporal-server.ts --codecEndpoint http://127.0.0.1:8888", + "codec-server": "esno ./scripts/start-codec-server.ts --port 8888", + "serve:playwright:e2e": "vite build && vite preview --mode test.e2e", + "serve:playwright:integration": "vite build && vite preview --mode test.integration --port 3333", "test": "TZ=UTC vitest", - "preview:local": "VITE_TEMPORAL_UI_BUILD_TARGET=local vite preview --port 3000", - "preview:cloud": "VITE_TEMPORAL_UI_BUILD_TARGET=cloud vite preview --port 3000", + "test:ui": "TZ=UTC vitest --ui", + "test:coverage": "TZ=UTC vitest run --coverage", + "test:e2e": "PW_MODE=e2e playwright test tests/e2e", + "test:e2e:ui": "pnpm test:e2e --ui", + "test:integration": "PW_MODE=integration playwright test tests/integration", + "test:integration:ui": "PW_MODE=integration playwright test --ui tests/integration", + "lint": "pnpm prettier; pnpm eslint; pnpm stylelint", + "lint:ci": "pnpm prettier && pnpm eslint && pnpm stylelint", + "format": "pnpm prettier:fix; pnpm eslint:fix; pnpm stylelint:fix", "check": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json", "check:watch": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json --watch", - "check:local": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json", - "check:local:watch": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json --watch", - "check:cloud": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json", - "check:cloud:watch": "VITE_TEMPORAL_UI_BUILD_TARGET=local svelte-check --tsconfig ./tsconfig.json --watch", - "lint": "prettier --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", - "format": "prettier --write --plugin-search-dir=. .", - "cypress": "concurrently \"pnpm run dev:test\" \"cypress open\"", - "cypress:run": "cypress run", - "package": "svelte-kit package && rsync -r ./package/lib/ ./package/ && rm -rf ./package/lib", - "package:patch": "pnpm version patch && svelte-kit package", - "package:minor": "pnpm version minor && svelte-kit package", - "package:major": "pnpm version major && svelte-kit package", - "snapshot": "vitest run --dir src/lib/holocene", - "story:dev": "histoire dev", - "story:build": "histoire build", - "story:preview": "histoire preview", - "coverage": "TZ=UTC vitest run --coverage", - "test-ui": "TZ=UTC vitest --ui" + "prettier": "prettier --check --plugin prettier-plugin-svelte --plugin prettier-plugin-tailwindcss .", + "prettier:fix": "prettier --write --plugin prettier-plugin-svelte --plugin prettier-plugin-tailwindcss .", + "preview:local": "VITE_TEMPORAL_UI_BUILD_TARGET=local vite preview", + "preview:docker": "VITE_API=http://localhost:8080 VITE_TEMPORAL_UI_BUILD_TARGET=local vite preview", + "package": "svelte-package", + "package:patch": "pnpm version patch && svelte-package", + "package:minor": "pnpm version minor && svelte-package", + "package:major": "pnpm version major && svelte-package", + "stories:dev": "storybook dev -p 6006", + "stories:build": "storybook build", + "stories:test": "test-storybook --index-json", + "stylelint": "stylelint \"src/**/*.{css,postcss,svelte}\"", + "stylelint:fix": "stylelint --fix \"src/**/*.{css,postcss,svelte}\"", + "generate:locales": "esno scripts/generate-locales.ts", + "workflows": "esno scripts/workflows.ts", + "audit:tailwind": "esno scripts/audit-tailwind-colors", + "audit:holocene-props": "esno scripts/generate-holocene-props.ts", + "validate:versions": "./scripts/validate-versions.sh" }, "dependencies": { + "@codemirror/autocomplete": "^6.17.0", + "@codemirror/commands": "^6.6.0", + "@codemirror/lang-json": "^6.0.1", + "@codemirror/language": "^6.10.2", + "@codemirror/legacy-modes": "^6.4.0", + "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.29.0", + "@fontsource-variable/inter": "^5.0.8", + "@fontsource/noto-sans-mono": "^5.0.9", + "@lezer/highlight": "^1.1.3", + "@sveltejs/package": "^2.3.10", "@sveltejs/svelte-virtual-list": "^3.0.1", - "date-fns": "^2.23.0", - "date-fns-tz": "^1.3.0", - "json-beautify": "^1.1.1", + "class-variance-authority": "^0.7.0", + "date-fns": "^2.29.3", + "date-fns-tz": "^1.3.7", + "esm-env": "^1.0.0", + "hast-util-sanitize": "^5.0.1", + "hast-util-to-html": "^9.0.1", + "hastscript": "^9.0.0", + "i18next": "^22.4.15", + "i18next-browser-languagedetector": "^7.0.1", "json-bigint": "^1.0.0", "just-debounce": "^1.1.0", - "sanitize-html": "^2.7.1", + "just-throttle": "^4.2.0", + "kebab-case": "^1.0.2", + "lodash.groupby": "^4.6.0", + "mdast-util-from-markdown": "^2.0.1", + "mdast-util-to-hast": "^13.2.0", + "monaco-editor": "^0.50.0", + "remark-stringify": "^10.0.3", + "sveltekit-superforms": "^2.26.1", + "tailwind-merge": "^1.14.0", + "unist-util-remove": "^4.0.0", "url-pattern": "^1.0.3", - "uuid": "^8.3.2", - "websocket-as-promised": "^2.0.1" + "uuid": "^9.0.0", + "zod": "^3.25.64" }, "devDependencies": { - "@babel/core": "^7.17.9", - "@babel/preset-typescript": "^7.16.7", - "@crownframework/svelte-error-boundary": "^1.0.3", - "@grpc/grpc-js": "^1.3.7", - "@histoire/controls": "^0.11.3", - "@histoire/plugin-svelte": "^0.11.3", - "@histoire/shared": "^0.11.3", - "@sveltejs/adapter-vercel": "1.0.0-next.77", - "@sveltejs/kit": "1.0.0-next.405", - "@sveltejs/vite-plugin-svelte": "^1.0.1", - "@temporalio/proto": "^1.4.4", + "@axe-core/playwright": "^4.10.1", + "@babel/core": "^7.20.12", + "@babel/preset-typescript": "^7.18.6", + "@chromatic-com/storybook": "^3.2.6", + "@grpc/grpc-js": "^1.8.22", + "@playwright/test": "^1.49.1", + "@storybook/addon-a11y": "^8.6.11", + "@storybook/addon-actions": "^8.6.4", + "@storybook/addon-docs": "^8.6.11", + "@storybook/addon-essentials": "^8.6.11", + "@storybook/addon-interactions": "^8.6.11", + "@storybook/addon-links": "^8.6.11", + "@storybook/addon-svelte-csf": "^5.0.0-next.23", + "@storybook/addon-themes": "^8.6.11", + "@storybook/blocks": "^8.6.11", + "@storybook/icons": "^1.4.0", + "@storybook/svelte": "^8.6.11", + "@storybook/sveltekit": "^8.6.11", + "@storybook/test": "^8.6.11", + "@storybook/test-runner": "^0.22.0", + "@sveltejs/adapter-static": "^3.0.8", + "@sveltejs/adapter-vercel": "^4.0.0", + "@sveltejs/kit": "^2.20.2", + "@sveltejs/vite-plugin-svelte": "^5.0.3", + "@temporalio/activity": "1.11.8", + "@temporalio/client": "1.11.8", + "@temporalio/common": "1.11.8", + "@temporalio/proto": "1.11.8", + "@temporalio/testing": "1.11.8", + "@temporalio/worker": "1.11.8", + "@temporalio/workflow": "1.11.8", "@types/base-64": "^1.0.0", - "@types/node": "^16.3.2", - "@types/sanitize-html": "^2.6.1", - "@typescript-eslint/eslint-plugin": "^5.28.0", - "@typescript-eslint/parser": "^5.28.0", - "@vitest/ui": "^0.16.0", - "autoprefixer": "^10.4.2", - "babel-loader": "^8.2.4", - "babel-preset-vite": "^1.0.4", - "c8": "^7.11.3", + "@types/cors": "^2.8.13", + "@types/express": "^4.17.17", + "@types/json-bigint": "^1.0.1", + "@types/mkdirp": "^1.0.2", + "@types/node": "^18.15.3", + "@types/sanitize-html": "^2.8.0", + "@types/tar-fs": "^2.0.4", + "@types/unist": "^3.0.0", + "@types/uuid": "^9.0.0", + "@types/yargs": "^17.0.24", + "@typescript-eslint/eslint-plugin": "^6.6.0", + "@typescript-eslint/parser": "^6.6.0", + "@vitest/ui": "^3.1.1", + "autoprefixer": "^10.4.13", + "axe-playwright": "^2.0.3", + "c8": "^7.12.0", "chalk": "^4.1.2", - "concurrently": "^7.1.0", - "cross-env": "^7.0.3", - "cssnano": "^5.0.8", - "cypress": "^9.5.3", - "esbuild": "^0.13.15", - "eslint": "^8.0.0", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-cypress": "^2.11.3", - "eslint-plugin-svelte3": "^3.2.0", - "eslint-plugin-vitest": "^0.0.8", - "google-protobuf": "^3.17.3", - "histoire": "^0.11.3", - "husky": "^8.0.1", - "jest-websocket-mock": "^2.2.1", - "jsdom": "^20.0.0", - "lint-staged": "^13.0.3", - "mock-socket": "^9.1.0", - "postcss": "^8.4.5", + "cors": "^2.8.5", + "cssnano": "^5.1.14", + "eslint": "^8.47.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-playwright": "^0.15.3", + "eslint-plugin-storybook": "^0.8.0", + "eslint-plugin-svelte": "^2.46.1", + "eslint-plugin-vitest": "^0.2.8", + "esno": "^0.16.3", + "express": "^4.18.2", + "fast-glob": "^3.3.1", + "google-protobuf": "^3.21.2", + "husky": "^8.0.3", + "jsdom": "^20.0.3", + "lint-staged": "^13.1.0", + "mkdirp": "^2.1.3", + "node-fetch": "^3.3.0", + "postcss": "^8.4.31", "postcss-cli": "^9.1.0", + "postcss-html": "^1.5.0", "postcss-import": "^14.1.0", - "postcss-load-config": "^3.1.0", - "prettier": "^2.7.1", - "prettier-plugin-tailwindcss": "^0.1.11", - "stylelint": "^14.6.1", - "stylelint-config-recommended": "^7.0.0", - "svelte": "^3.52.0", - "svelte-check": "^2.4.5", - "svelte-highlight": "^3.4.0", - "svelte-loader": "^3.1.2", - "svelte-preprocess": "^4.10.4", - "svelte2tsx": "^0.5.10", - "tailwindcss": "^3.1.4", - "ts-node": "^10.2.1", - "ts-proto": "^1.82.5", - "tslib": "^2.3.1", - "typescript": "^4.4.2", - "vite": "^3.1.0", - "vite-node": "^0.23.2", - "vitest": "^0.15.2", - "webpack": "^5.73.0" + "postcss-load-config": "^3.1.4", + "prettier": "^3.5.1", + "prettier-plugin-svelte": "^3.3.3", + "prettier-plugin-tailwindcss": "^0.5.4", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "rehype-document": "^6.1.0", + "rehype-format": "^4.0.1", + "rehype-stringify": "^9.0.4", + "remark": "^14.0.3", + "remark-gfm": "^3.0.1", + "remark-parse": "^10.0.2", + "remark-toc": "^8.0.1", + "rimraf": "^4.3.1", + "storybook": "^8.6.11", + "stylelint": "^15.10.3", + "stylelint-config-recommended": "^13.0.0", + "stylelint-config-standard": "^34.0.0", + "svelte": "^5.25.5", + "svelte-check": "^4.1.5", + "svelte-eslint-parser": "^0.43.0", + "svelte-highlight": "^7.8.2", + "svelte-loader": "^3.2.4", + "svelte-preprocess": "^6.0.3", + "svelte2tsx": "^0.7.35", + "tailwindcss": "^3.4.1", + "tar-fs": ">=2.1.2", + "tslib": "^2.4.1", + "typescript": "^5.2.2", + "unist-util-visit": "^5.0.0", + "vite": "^6.2.4", + "vitest": "^3.1.1", + "vitest-localstorage-mock": "^0.1.2", + "wait-port": "^1.0.4", + "webpack": "^5.76.0", + "yargs": "^17.7.2", + "zx": "^7.1.1" }, "peerDependencies": { + "@sveltejs/kit": "^2.20.2", "date-fns": "2.29.x", "date-fns-tz": "1.3.x", - "websocket-as-promised": "2.0.x" + "svelte": "^5.25.5" }, - "lint-staged": { - "*.{js,ts,json,css,svelte}": "pnpm format" - } + "packageManager": "pnpm@8.15.7+sha256.50783dd0fa303852de2dd1557cd4b9f07cb5b018154a6e76d0f40635d6cee019" } diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 000000000..9ab23d275 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,62 @@ +import { defineConfig, devices } from '@playwright/test'; + +const PLAYWRIGHT_MODE = process.env.PW_MODE; +const PORT = PLAYWRIGHT_MODE === 'e2e' ? 3000 : 3333; + +export default defineConfig({ + testDir: './tests', + timeout: 10 * 1000, + expect: { + timeout: 10000, + }, + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 3 : 1, + workers: process.env.CI ? 1 : undefined, + reporter: [ + ['html'], + ['json', { outputFile: 'playwright-report/test-results.json' }], + [process.env.CI ? 'github' : 'list'], + [ + './tests/test-utilities/accessibility-reporter', + { outputFile: 'playwright-report/accessibility-violations.json' }, + ], + ], + use: { + actionTimeout: 0, + baseURL: `http://localhost:${PORT}`, + trace: 'retain-on-failure', + timezoneId: 'America/Denver', + storageState: `./tests/${PLAYWRIGHT_MODE}/storageState.json`, + }, + projects: [ + { + name: 'chromium desktop', + testIgnore: /.*mobile.spec.ts/, + use: { + ...devices['Desktop Chrome'], + viewport: { width: 1920, height: 1080 }, + isMobile: false, + }, + }, + { + name: 'chromium mobile', + testIgnore: /.*desktop.spec.ts/, + use: { + ...devices['Desktop Chrome'], + viewport: { width: 320, height: 800 }, + isMobile: true, + }, + }, + ], + webServer: { + timeout: 2 * 60 * 1000, + command: `pnpm serve:playwright:${PLAYWRIGHT_MODE}`, + port: PORT, + }, + globalSetup: './tests/global-setup.ts', + globalTeardown: './tests/global-teardown.ts', + metadata: { + mode: PLAYWRIGHT_MODE, + }, +}); diff --git a/plugins/vite-plugin-temporal-server.ts b/plugins/vite-plugin-temporal-server.ts new file mode 100644 index 000000000..7a8b37402 --- /dev/null +++ b/plugins/vite-plugin-temporal-server.ts @@ -0,0 +1,104 @@ +import type { Plugin } from 'vite'; +import type { ViteDevServer } from 'vite'; +import { chalk } from 'zx'; + +import { + createTemporalServer, + type TemporalServer, +} from '../utilities/temporal-server'; + +const { cyan, magenta } = chalk; + +let temporal: TemporalServer; + +const shouldSkip = (server: ViteDevServer): boolean => { + if (process.env.VERCEL) return true; + if (process.env.VITEST) return true; + if (temporal) return true; + if (process.platform === 'win32') return true; + if (!['temporal-server', 'ui-server'].includes(server.config.mode)) + return true; + + return false; +}; + +const persistentDB = (server: ViteDevServer): string | undefined => { + const filename = server.config.env.VITE_TEMPORAL_DB; + if (!filename) { + console.warn( + magenta( + 'No VITE_TEMPORAL_DB environment variable set. Using in-memory database.', + ), + ); + return; + } + + console.log(magenta(`Using persistent database at ${filename}.`)); + + return filename; +}; + +const getPortFromApiEndpoint = (endpoint: string, fallback = 8233): number => { + return validatePort( + endpoint.slice(endpoint.lastIndexOf(':') + 1, endpoint.length), + fallback, + ); +}; + +const isValidPort = (port: number): boolean => { + if (typeof port !== 'number') return false; + if (isNaN(port)) return false; + if (port <= 1024) return false; + if (port > 65536) return false; + return true; +}; + +const validatePort = (port: number | string, fallback: number): number => { + port = Number(port); + + if (isValidPort(port)) return port; + + console.error(`${port} is not a valid port. Falling back to ${fallback}.`); + + if (isValidPort(fallback)) return fallback; + + throw new Error( + `Both the provided port, ${port}, and its fallback, ${fallback}, are invalid ports.`, + ); +}; + +export function temporalServer(): Plugin { + return { + name: 'vite-plugin-temporal-server', + enforce: 'post', + apply: 'serve', + async configureServer(server) { + if (shouldSkip(server)) return; + + const port = validatePort(server.config.env.VITE_TEMPORAL_PORT, 7233); + const uiPort = getPortFromApiEndpoint(server.config.env.VITE_API); + + console.log(magenta(`Starting Temporal Server on Port ${port}…`)); + console.log(cyan(`Starting Temporal UI Server on Port ${uiPort}…`)); + + temporal = await createTemporalServer({ + port, + uiPort, + dbFilename: persistentDB(server), + }); + + await temporal.ready(); + + console.log(magenta(`Temporal Server is running on Port ${port}.`)); + console.log(cyan(`Temporal UI Server is running on Port ${uiPort}.`)); + }, + async closeBundle() { + await temporal?.shutdown(); + }, + }; +} + +process.on('beforeExit', async () => { + if (!temporal) return; + await temporal?.shutdown(); +}); diff --git a/plugins/vite-plugin-ui-server.ts b/plugins/vite-plugin-ui-server.ts new file mode 100644 index 000000000..4568df7fc --- /dev/null +++ b/plugins/vite-plugin-ui-server.ts @@ -0,0 +1,37 @@ +import type { Plugin } from 'vite'; +import type { ViteDevServer } from 'vite'; + +import { createUIServer, type UIServer } from '../utilities/ui-server'; + +let uiServer: UIServer; + +const shouldSkip = (server: ViteDevServer): boolean => { + if (process.env.VERCEL) return true; + if (process.env.VITEST) return true; + if (process.env.CI) return true; + if (['ui-server', 'local-temporal'].includes(server.config.mode)) + return false; + + return true; +}; + +export function uiServerPlugin(): Plugin { + return { + name: 'vite-plugin-ui-server', + enforce: 'post', + apply: 'serve', + async configureServer(server) { + if (shouldSkip(server)) return; + uiServer = await createUIServer(); + await uiServer.ready(); + }, + async closeBundle() { + await uiServer?.shutdown(); + }, + }; +} + +process.on('beforeExit', async () => { + if (!uiServer) return; + await uiServer?.shutdown(); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e25d107bc..578847c94 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,7363 +1,10292 @@ -lockfileVersion: 5.4 - -specifiers: - '@babel/core': ^7.17.9 - '@babel/preset-typescript': ^7.16.7 - '@crownframework/svelte-error-boundary': ^1.0.3 - '@grpc/grpc-js': ^1.3.7 - '@histoire/controls': ^0.11.3 - '@histoire/plugin-svelte': ^0.11.3 - '@histoire/shared': ^0.11.3 - '@sveltejs/adapter-vercel': 1.0.0-next.77 - '@sveltejs/kit': 1.0.0-next.405 - '@sveltejs/svelte-virtual-list': ^3.0.1 - '@sveltejs/vite-plugin-svelte': ^1.0.1 - '@temporalio/proto': ^1.4.4 - '@types/base-64': ^1.0.0 - '@types/node': ^16.3.2 - '@types/sanitize-html': ^2.6.1 - '@typescript-eslint/eslint-plugin': ^5.28.0 - '@typescript-eslint/parser': ^5.28.0 - '@vitest/ui': ^0.16.0 - autoprefixer: ^10.4.2 - babel-loader: ^8.2.4 - babel-preset-vite: ^1.0.4 - c8: ^7.11.3 - chalk: ^4.1.2 - concurrently: ^7.1.0 - cross-env: ^7.0.3 - cssnano: ^5.0.8 - cypress: ^9.5.3 - date-fns: ^2.23.0 - date-fns-tz: ^1.3.0 - esbuild: ^0.13.15 - eslint: ^8.0.0 - eslint-config-prettier: ^8.3.0 - eslint-plugin-cypress: ^2.11.3 - eslint-plugin-svelte3: ^3.2.0 - eslint-plugin-vitest: ^0.0.8 - google-protobuf: ^3.17.3 - histoire: ^0.11.3 - husky: ^8.0.1 - jest-websocket-mock: ^2.2.1 - jsdom: ^20.0.0 - json-beautify: ^1.1.1 - json-bigint: ^1.0.0 - just-debounce: ^1.1.0 - lint-staged: ^13.0.3 - mock-socket: ^9.1.0 - postcss: ^8.4.5 - postcss-cli: ^9.1.0 - postcss-import: ^14.1.0 - postcss-load-config: ^3.1.0 - prettier: ^2.7.1 - prettier-plugin-tailwindcss: ^0.1.11 - sanitize-html: ^2.7.1 - stylelint: ^14.6.1 - stylelint-config-recommended: ^7.0.0 - svelte: ^3.52.0 - svelte-check: ^2.4.5 - svelte-highlight: ^3.4.0 - svelte-loader: ^3.1.2 - svelte-preprocess: ^4.10.4 - svelte2tsx: ^0.5.10 - tailwindcss: ^3.1.4 - ts-node: ^10.2.1 - ts-proto: ^1.82.5 - tslib: ^2.3.1 - typescript: ^4.4.2 - url-pattern: ^1.0.3 - uuid: ^8.3.2 - vite: ^3.1.0 - vite-node: ^0.23.2 - vitest: ^0.15.2 - webpack: ^5.73.0 - websocket-as-promised: ^2.0.1 +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false dependencies: - '@sveltejs/svelte-virtual-list': 3.0.1 - date-fns: 2.28.0 - date-fns-tz: 1.3.4_date-fns@2.28.0 - json-beautify: 1.1.1 - json-bigint: 1.0.0 - just-debounce: 1.1.0 - sanitize-html: 2.7.1 - url-pattern: 1.0.3 - uuid: 8.3.2 - websocket-as-promised: 2.0.1 + '@codemirror/autocomplete': + specifier: ^6.17.0 + version: 6.17.0(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.29.0)(@lezer/common@1.2.3) + '@codemirror/commands': + specifier: ^6.6.0 + version: 6.6.0 + '@codemirror/lang-json': + specifier: ^6.0.1 + version: 6.0.1 + '@codemirror/language': + specifier: ^6.10.2 + version: 6.10.2 + '@codemirror/legacy-modes': + specifier: ^6.4.0 + version: 6.4.0 + '@codemirror/state': + specifier: ^6.4.1 + version: 6.4.1 + '@codemirror/view': + specifier: ^6.29.0 + version: 6.29.0 + '@fontsource-variable/inter': + specifier: ^5.0.8 + version: 5.0.8 + '@fontsource/noto-sans-mono': + specifier: ^5.0.9 + version: 5.0.9 + '@lezer/highlight': + specifier: ^1.1.3 + version: 1.1.3 + '@sveltejs/package': + specifier: ^2.3.10 + version: 2.3.10(svelte@5.25.5)(typescript@5.2.2) + '@sveltejs/svelte-virtual-list': + specifier: ^3.0.1 + version: 3.0.1 + class-variance-authority: + specifier: ^0.7.0 + version: 0.7.0 + date-fns: + specifier: ^2.29.3 + version: 2.29.3 + date-fns-tz: + specifier: ^1.3.7 + version: 1.3.7(date-fns@2.29.3) + esm-env: + specifier: ^1.0.0 + version: 1.0.0 + hast-util-sanitize: + specifier: ^5.0.1 + version: 5.0.1 + hast-util-to-html: + specifier: ^9.0.1 + version: 9.0.1 + hastscript: + specifier: ^9.0.0 + version: 9.0.0 + i18next: + specifier: ^22.4.15 + version: 22.4.15 + i18next-browser-languagedetector: + specifier: ^7.0.1 + version: 7.0.1 + json-bigint: + specifier: ^1.0.0 + version: 1.0.0 + just-debounce: + specifier: ^1.1.0 + version: 1.1.0 + just-throttle: + specifier: ^4.2.0 + version: 4.2.0 + kebab-case: + specifier: ^1.0.2 + version: 1.0.2 + lodash.groupby: + specifier: ^4.6.0 + version: 4.6.0 + mdast-util-from-markdown: + specifier: ^2.0.1 + version: 2.0.1 + mdast-util-to-hast: + specifier: ^13.2.0 + version: 13.2.0 + monaco-editor: + specifier: ^0.50.0 + version: 0.50.0 + remark-stringify: + specifier: ^10.0.3 + version: 10.0.3 + sveltekit-superforms: + specifier: ^2.26.1 + version: 2.27.1(@sveltejs/kit@2.20.2)(esbuild@0.25.0)(svelte@5.25.5)(typescript@5.2.2) + tailwind-merge: + specifier: ^1.14.0 + version: 1.14.0 + unist-util-remove: + specifier: ^4.0.0 + version: 4.0.0 + url-pattern: + specifier: ^1.0.3 + version: 1.0.3 + uuid: + specifier: ^9.0.0 + version: 9.0.0 + zod: + specifier: ^3.25.64 + version: 3.25.67 devDependencies: - '@babel/core': 7.17.12 - '@babel/preset-typescript': 7.17.12_@babel+core@7.17.12 - '@crownframework/svelte-error-boundary': 1.0.3 - '@grpc/grpc-js': 1.6.7 - '@histoire/controls': 0.11.3 - '@histoire/plugin-svelte': 0.11.3_y5gzbyoew4hvqk2lyjp3cltjx4 - '@histoire/shared': 0.11.3_vite@3.1.0 - '@sveltejs/adapter-vercel': 1.0.0-next.77 - '@sveltejs/kit': 1.0.0-next.405_svelte@3.52.0+vite@3.1.0 - '@sveltejs/vite-plugin-svelte': 1.0.5_svelte@3.52.0+vite@3.1.0 - '@temporalio/proto': 1.4.4 - '@types/base-64': 1.0.0 - '@types/node': 16.11.39 - '@types/sanitize-html': 2.6.2 - '@typescript-eslint/eslint-plugin': 5.36.2_wuspq44aiwbrzu4wm6rgbxcdsu - '@typescript-eslint/parser': 5.36.2_pyvvhc3zqdua4akflcggygkl44 - '@vitest/ui': 0.16.0 - autoprefixer: 10.4.7_postcss@8.4.14 - babel-loader: 8.2.5_upeuvotedl3gkw7tmcpzrrrck4 - babel-preset-vite: 1.0.4 - c8: 7.11.3 - chalk: 4.1.2 - concurrently: 7.2.0 - cross-env: 7.0.3 - cssnano: 5.1.8_postcss@8.4.14 - cypress: 9.7.0 - esbuild: 0.13.15 - eslint: 8.23.0 - eslint-config-prettier: 8.5.0_eslint@8.23.0 - eslint-plugin-cypress: 2.12.1_eslint@8.23.0 - eslint-plugin-svelte3: 3.4.1_frfkdjwsrgdj4v6yq5by2hqlde - eslint-plugin-vitest: 0.0.8_pyvvhc3zqdua4akflcggygkl44 - google-protobuf: 3.20.1 - histoire: 0.11.3_vite@3.1.0 - husky: 8.0.1 - jest-websocket-mock: 2.3.0 - jsdom: 20.0.0 - lint-staged: 13.0.3 - mock-socket: 9.1.3 - postcss: 8.4.14 - postcss-cli: 9.1.0_gimuzhxoryaoe5ripiipjtmpcu - postcss-import: 14.1.0_postcss@8.4.14 - postcss-load-config: 3.1.4_gimuzhxoryaoe5ripiipjtmpcu - prettier: 2.7.1 - prettier-plugin-tailwindcss: 0.1.11_prettier@2.7.1 - stylelint: 14.8.2 - stylelint-config-recommended: 7.0.0_stylelint@14.8.2 - svelte: 3.52.0 - svelte-check: 2.7.1_irkco2cxth74o7nd7xqin6hsem - svelte-highlight: 3.4.0 - svelte-loader: 3.1.2_svelte@3.52.0 - svelte-preprocess: 4.10.6_mzb65lhesrxcsbtmqpxwfdasfq - svelte2tsx: 0.5.10_r4lkni65x73edzylyqv3pim744 - tailwindcss: 3.1.4_ts-node@10.7.0 - ts-node: 10.7.0_2jmrorcux6nsf7sy4ly74hophu - ts-proto: 1.112.2 - tslib: 2.4.0 - typescript: 4.6.4 - vite: 3.1.0 - vite-node: 0.23.2 - vitest: 0.15.2_dcowuwleuujrodfztzblsok4y4 - webpack: 5.73.0_esbuild@0.13.15 + '@axe-core/playwright': + specifier: ^4.10.1 + version: 4.10.1(playwright-core@1.52.0) + '@babel/core': + specifier: ^7.20.12 + version: 7.20.12 + '@babel/preset-typescript': + specifier: ^7.18.6 + version: 7.18.6(@babel/core@7.20.12) + '@chromatic-com/storybook': + specifier: ^3.2.6 + version: 3.2.6(react@18.2.0)(storybook@8.6.11) + '@grpc/grpc-js': + specifier: ^1.8.22 + version: 1.10.9 + '@playwright/test': + specifier: ^1.49.1 + version: 1.49.1 + '@storybook/addon-a11y': + specifier: ^8.6.11 + version: 8.6.11(storybook@8.6.11) + '@storybook/addon-actions': + specifier: ^8.6.4 + version: 8.6.4(storybook@8.6.11) + '@storybook/addon-docs': + specifier: ^8.6.11 + version: 8.6.11(@types/react@19.1.2)(storybook@8.6.11) + '@storybook/addon-essentials': + specifier: ^8.6.11 + version: 8.6.11(@types/react@19.1.2)(storybook@8.6.11) + '@storybook/addon-interactions': + specifier: ^8.6.11 + version: 8.6.11(storybook@8.6.11) + '@storybook/addon-links': + specifier: ^8.6.11 + version: 8.6.11(react@18.2.0)(storybook@8.6.11) + '@storybook/addon-svelte-csf': + specifier: ^5.0.0-next.23 + version: 5.0.0-next.27(@storybook/svelte@8.6.11)(@sveltejs/vite-plugin-svelte@5.0.3)(storybook@8.6.11)(svelte@5.25.5)(vite@6.2.7) + '@storybook/addon-themes': + specifier: ^8.6.11 + version: 8.6.11(storybook@8.6.11) + '@storybook/blocks': + specifier: ^8.6.11 + version: 8.6.11(react-dom@18.2.0)(react@18.2.0)(storybook@8.6.11) + '@storybook/icons': + specifier: ^1.4.0 + version: 1.4.0(react-dom@18.2.0)(react@18.2.0) + '@storybook/svelte': + specifier: ^8.6.11 + version: 8.6.11(storybook@8.6.11)(svelte@5.25.5) + '@storybook/sveltekit': + specifier: ^8.6.11 + version: 8.6.11(@babel/core@7.20.12)(@sveltejs/vite-plugin-svelte@5.0.3)(postcss-load-config@3.1.4)(postcss@8.4.31)(storybook@8.6.11)(svelte@5.25.5)(vite@6.2.7) + '@storybook/test': + specifier: ^8.6.11 + version: 8.6.11(storybook@8.6.11) + '@storybook/test-runner': + specifier: ^0.22.0 + version: 0.22.0(@types/node@18.15.3)(storybook@8.6.11) + '@sveltejs/adapter-static': + specifier: ^3.0.8 + version: 3.0.8(@sveltejs/kit@2.20.2) + '@sveltejs/adapter-vercel': + specifier: ^4.0.0 + version: 4.0.0(@sveltejs/kit@2.20.2) + '@sveltejs/kit': + specifier: ^2.20.2 + version: 2.20.2(@sveltejs/vite-plugin-svelte@5.0.3)(svelte@5.25.5)(vite@6.2.7) + '@sveltejs/vite-plugin-svelte': + specifier: ^5.0.3 + version: 5.0.3(svelte@5.25.5)(vite@6.2.7) + '@temporalio/activity': + specifier: 1.11.8 + version: 1.11.8 + '@temporalio/client': + specifier: 1.11.8 + version: 1.11.8 + '@temporalio/common': + specifier: 1.11.8 + version: 1.11.8 + '@temporalio/proto': + specifier: 1.11.8 + version: 1.11.8 + '@temporalio/testing': + specifier: 1.11.8 + version: 1.11.8(esbuild@0.25.0) + '@temporalio/worker': + specifier: 1.11.8 + version: 1.11.8(esbuild@0.25.0) + '@temporalio/workflow': + specifier: 1.11.8 + version: 1.11.8 + '@types/base-64': + specifier: ^1.0.0 + version: 1.0.2 + '@types/cors': + specifier: ^2.8.13 + version: 2.8.13 + '@types/express': + specifier: ^4.17.17 + version: 4.17.17 + '@types/json-bigint': + specifier: ^1.0.1 + version: 1.0.1 + '@types/mkdirp': + specifier: ^1.0.2 + version: 1.0.2 + '@types/node': + specifier: ^18.15.3 + version: 18.15.3 + '@types/sanitize-html': + specifier: ^2.8.0 + version: 2.8.0 + '@types/tar-fs': + specifier: ^2.0.4 + version: 2.0.4 + '@types/unist': + specifier: ^3.0.0 + version: 3.0.0 + '@types/uuid': + specifier: ^9.0.0 + version: 9.0.0 + '@types/yargs': + specifier: ^17.0.24 + version: 17.0.24 + '@typescript-eslint/eslint-plugin': + specifier: ^6.6.0 + version: 6.6.0(@typescript-eslint/parser@6.6.0)(eslint@8.47.0)(typescript@5.2.2) + '@typescript-eslint/parser': + specifier: ^6.6.0 + version: 6.6.0(eslint@8.47.0)(typescript@5.2.2) + '@vitest/ui': + specifier: ^3.1.1 + version: 3.1.1(vitest@3.1.1) + autoprefixer: + specifier: ^10.4.13 + version: 10.4.13(postcss@8.4.31) + axe-playwright: + specifier: ^2.0.3 + version: 2.0.3(playwright@1.52.0) + c8: + specifier: ^7.12.0 + version: 7.12.0 + chalk: + specifier: ^4.1.2 + version: 4.1.2 + cors: + specifier: ^2.8.5 + version: 2.8.5 + cssnano: + specifier: ^5.1.14 + version: 5.1.14(postcss@8.4.31) + eslint: + specifier: ^8.47.0 + version: 8.47.0 + eslint-config-prettier: + specifier: ^9.0.0 + version: 9.0.0(eslint@8.47.0) + eslint-plugin-import: + specifier: ^2.28.1 + version: 2.28.1(@typescript-eslint/parser@6.6.0)(eslint@8.47.0) + eslint-plugin-playwright: + specifier: ^0.15.3 + version: 0.15.3(eslint@8.47.0) + eslint-plugin-storybook: + specifier: ^0.8.0 + version: 0.8.0(eslint@8.47.0)(typescript@5.2.2) + eslint-plugin-svelte: + specifier: ^2.46.1 + version: 2.46.1(eslint@8.47.0)(svelte@5.25.5) + eslint-plugin-vitest: + specifier: ^0.2.8 + version: 0.2.8(eslint@8.47.0)(typescript@5.2.2)(vite@6.2.7)(vitest@3.1.1) + esno: + specifier: ^0.16.3 + version: 0.16.3 + express: + specifier: ^4.18.2 + version: 4.20.0 + fast-glob: + specifier: ^3.3.1 + version: 3.3.1 + google-protobuf: + specifier: ^3.21.2 + version: 3.21.2 + husky: + specifier: ^8.0.3 + version: 8.0.3 + jsdom: + specifier: ^20.0.3 + version: 20.0.3 + lint-staged: + specifier: ^13.1.0 + version: 13.1.0 + mkdirp: + specifier: ^2.1.3 + version: 2.1.3 + node-fetch: + specifier: ^3.3.0 + version: 3.3.0 + postcss: + specifier: ^8.4.31 + version: 8.4.31 + postcss-cli: + specifier: ^9.1.0 + version: 9.1.0(postcss@8.4.31) + postcss-html: + specifier: ^1.5.0 + version: 1.5.0 + postcss-import: + specifier: ^14.1.0 + version: 14.1.0(postcss@8.4.31) + postcss-load-config: + specifier: ^3.1.4 + version: 3.1.4(postcss@8.4.31) + prettier: + specifier: ^3.5.1 + version: 3.5.3 + prettier-plugin-svelte: + specifier: ^3.3.3 + version: 3.3.3(prettier@3.5.3)(svelte@5.25.5) + prettier-plugin-tailwindcss: + specifier: ^0.5.4 + version: 0.5.4(prettier-plugin-svelte@3.3.3)(prettier@3.5.3) + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + rehype-document: + specifier: ^6.1.0 + version: 6.1.0 + rehype-format: + specifier: ^4.0.1 + version: 4.0.1 + rehype-stringify: + specifier: ^9.0.4 + version: 9.0.4 + remark: + specifier: ^14.0.3 + version: 14.0.3 + remark-gfm: + specifier: ^3.0.1 + version: 3.0.1 + remark-parse: + specifier: ^10.0.2 + version: 10.0.2 + remark-toc: + specifier: ^8.0.1 + version: 8.0.1 + rimraf: + specifier: ^4.3.1 + version: 4.3.1 + storybook: + specifier: ^8.6.11 + version: 8.6.11(prettier@3.5.3) + stylelint: + specifier: ^15.10.3 + version: 15.10.3 + stylelint-config-recommended: + specifier: ^13.0.0 + version: 13.0.0(stylelint@15.10.3) + stylelint-config-standard: + specifier: ^34.0.0 + version: 34.0.0(stylelint@15.10.3) + svelte: + specifier: ^5.25.5 + version: 5.25.5 + svelte-check: + specifier: ^4.1.5 + version: 4.1.5(svelte@5.25.5)(typescript@5.2.2) + svelte-eslint-parser: + specifier: ^0.43.0 + version: 0.43.0(svelte@5.25.5) + svelte-highlight: + specifier: ^7.8.2 + version: 7.8.2 + svelte-loader: + specifier: ^3.2.4 + version: 3.2.4(svelte@5.25.5) + svelte-preprocess: + specifier: ^6.0.3 + version: 6.0.3(@babel/core@7.20.12)(postcss-load-config@3.1.4)(postcss@8.4.31)(svelte@5.25.5)(typescript@5.2.2) + svelte2tsx: + specifier: ^0.7.35 + version: 0.7.35(svelte@5.25.5)(typescript@5.2.2) + tailwindcss: + specifier: ^3.4.1 + version: 3.4.1 + tar-fs: + specifier: '>=2.1.2' + version: 3.0.9 + tslib: + specifier: ^2.4.1 + version: 2.4.1 + typescript: + specifier: ^5.2.2 + version: 5.2.2 + unist-util-visit: + specifier: ^5.0.0 + version: 5.0.0 + vite: + specifier: ^6.2.4 + version: 6.2.7(@types/node@18.15.3) + vitest: + specifier: ^3.1.1 + version: 3.1.1(@types/node@18.15.3)(@vitest/ui@3.1.1)(jsdom@20.0.3) + vitest-localstorage-mock: + specifier: ^0.1.2 + version: 0.1.2(vitest@3.1.1) + wait-port: + specifier: ^1.0.4 + version: 1.0.4 + webpack: + specifier: ^5.76.0 + version: 5.94.0(@swc/core@1.10.7)(esbuild@0.25.0) + yargs: + specifier: ^17.7.2 + version: 17.7.2 + zx: + specifier: ^7.1.1 + version: 7.1.1 packages: - /@ampproject/remapping/2.2.0: - resolution: - { - integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==, - } - engines: { node: '>=6.0.0' } - dependencies: - '@jridgewell/gen-mapping': 0.1.1 - '@jridgewell/trace-mapping': 0.3.13 - dev: true - /@babel/code-frame/7.16.7: - resolution: - { - integrity: sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==, - } - engines: { node: '>=6.9.0' } - dependencies: - '@babel/highlight': 7.17.12 + /@aashutoshrathi/word-wrap@1.2.6: + resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} + engines: {node: '>=0.10.0'} dev: true - /@babel/compat-data/7.17.10: - resolution: - { - integrity: sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==, - } - engines: { node: '>=6.9.0' } + /@adobe/css-tools@4.4.1: + resolution: {integrity: sha512-12WGKBQzjUAI4ayyF4IAtfw2QR/IDoqk6jTddXDhtYTJF9ASmoE1zst7cVtP0aL/F1jUJL5r+JxKXKEgHNbEUQ==} dev: true - /@babel/core/7.17.12: - resolution: - { - integrity: sha512-44ODe6O1IVz9s2oJE3rZ4trNNKTX9O7KpQpfAP4t8QII/zwrVRHL7i2pxhqtcY7tqMLrrKfMlBKnm1QlrRFs5w==, - } - engines: { node: '>=6.9.0' } - dependencies: - '@ampproject/remapping': 2.2.0 - '@babel/code-frame': 7.16.7 - '@babel/generator': 7.17.12 - '@babel/helper-compilation-targets': 7.17.10_@babel+core@7.17.12 - '@babel/helper-module-transforms': 7.17.12 - '@babel/helpers': 7.17.9 - '@babel/parser': 7.17.12 - '@babel/template': 7.16.7 - '@babel/traverse': 7.17.12 - '@babel/types': 7.17.12 - convert-source-map: 1.8.0 - debug: 4.3.4 - gensync: 1.0.0-beta.2 - json5: 2.2.1 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color + /@alloc/quick-lru@5.2.0: + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} dev: true - /@babel/generator/7.17.12: - resolution: - { - integrity: sha512-V49KtZiiiLjH/CnIW6OjJdrenrGoyh6AmKQ3k2AZFKozC1h846Q4NYlZ5nqAigPDUXfGzC88+LOUuG8yKd2kCw==, - } - engines: { node: '>=6.9.0' } + /@ampproject/remapping@2.2.0: + resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} + engines: {node: '>=6.0.0'} dependencies: - '@babel/types': 7.17.12 - '@jridgewell/gen-mapping': 0.3.1 - jsesc: 2.5.2 + '@jridgewell/gen-mapping': 0.1.1 + '@jridgewell/trace-mapping': 0.3.25 dev: true - /@babel/helper-annotate-as-pure/7.16.7: - resolution: - { - integrity: sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==, - } - engines: { node: '>=6.9.0' } + /@ampproject/remapping@2.3.0: + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} dependencies: - '@babel/types': 7.17.12 - dev: true + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 - /@babel/helper-compilation-targets/7.17.10_@babel+core@7.17.12: - resolution: - { - integrity: sha512-gh3RxjWbauw/dFiU/7whjd0qN9K6nPJMqe6+Er7rOavFh0CQUSwhAE3IcTho2rywPJFxej6TUUHDkWcYI6gGqQ==, - } - engines: { node: '>=6.9.0' } - peerDependencies: - '@babel/core': ^7.0.0 + /@ark/schema@0.46.0: + resolution: {integrity: sha512-c2UQdKgP2eqqDArfBqQIJppxJHvNNXuQPeuSPlDML4rjw+f1cu0qAlzOG4b8ujgm9ctIDWwhpyw6gjG5ledIVQ==} + requiresBuild: true dependencies: - '@babel/compat-data': 7.17.10 - '@babel/core': 7.17.12 - '@babel/helper-validator-option': 7.16.7 - browserslist: 4.20.3 - semver: 6.3.0 - dev: true + '@ark/util': 0.46.0 + dev: false + optional: true + + /@ark/util@0.46.0: + resolution: {integrity: sha512-JPy/NGWn/lvf1WmGCPw2VGpBg5utZraE84I7wli18EDF3p3zc/e9WolT35tINeZO3l7C77SjqRJeAUoT0CvMRg==} + requiresBuild: true + dev: false + optional: true - /@babel/helper-create-class-features-plugin/7.17.12_@babel+core@7.17.12: - resolution: - { - integrity: sha512-sZoOeUTkFJMyhqCei2+Z+wtH/BehW8NVKQt7IRUQlRiOARuXymJYfN/FCcI8CvVbR0XVyDM6eLFOlR7YtiXnew==, - } - engines: { node: '>=6.9.0' } + /@axe-core/playwright@4.10.1(playwright-core@1.52.0): + resolution: {integrity: sha512-EV5t39VV68kuAfMKqb/RL+YjYKhfuGim9rgIaQ6Vntb2HgaCaau0h98Y3WEUqW1+PbdzxDtDNjFAipbtZuBmEA==} peerDependencies: - '@babel/core': ^7.0.0 + playwright-core: '>= 1.0.0' dependencies: - '@babel/core': 7.17.12 - '@babel/helper-annotate-as-pure': 7.16.7 - '@babel/helper-environment-visitor': 7.16.7 - '@babel/helper-function-name': 7.17.9 - '@babel/helper-member-expression-to-functions': 7.17.7 - '@babel/helper-optimise-call-expression': 7.16.7 - '@babel/helper-replace-supers': 7.16.7 - '@babel/helper-split-export-declaration': 7.16.7 - transitivePeerDependencies: - - supports-color + axe-core: 4.10.2 + playwright-core: 1.52.0 dev: true - /@babel/helper-environment-visitor/7.16.7: - resolution: - { - integrity: sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==, - } - engines: { node: '>=6.9.0' } + /@babel/code-frame@7.18.6: + resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} + engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.17.12 + '@babel/highlight': 7.18.6 dev: true - /@babel/helper-function-name/7.17.9: - resolution: - { - integrity: sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg==, - } - engines: { node: '>=6.9.0' } + /@babel/code-frame@7.22.13: + resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==} + engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.16.7 - '@babel/types': 7.17.12 + '@babel/highlight': 7.22.20 + chalk: 2.4.2 dev: true - /@babel/helper-hoist-variables/7.16.7: - resolution: - { - integrity: sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==, - } - engines: { node: '>=6.9.0' } + /@babel/code-frame@7.24.7: + resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.17.12 + '@babel/highlight': 7.24.7 + picocolors: 1.1.1 dev: true - /@babel/helper-member-expression-to-functions/7.17.7: - resolution: - { - integrity: sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==, - } - engines: { node: '>=6.9.0' } - dependencies: - '@babel/types': 7.17.12 + /@babel/compat-data@7.20.10: + resolution: {integrity: sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==} + engines: {node: '>=6.9.0'} dev: true - /@babel/helper-module-imports/7.16.7: - resolution: - { - integrity: sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==, - } - engines: { node: '>=6.9.0' } - dependencies: - '@babel/types': 7.17.12 + /@babel/compat-data@7.24.7: + resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==} + engines: {node: '>=6.9.0'} dev: true - /@babel/helper-module-transforms/7.17.12: - resolution: - { - integrity: sha512-t5s2BeSWIghhFRPh9XMn6EIGmvn8Lmw5RVASJzkIx1mSemubQQBNIZiQD7WzaFmaHIrjAec4x8z9Yx8SjJ1/LA==, - } - engines: { node: '>=6.9.0' } + /@babel/core@7.20.12: + resolution: {integrity: sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==} + engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-environment-visitor': 7.16.7 - '@babel/helper-module-imports': 7.16.7 - '@babel/helper-simple-access': 7.17.7 - '@babel/helper-split-export-declaration': 7.16.7 - '@babel/helper-validator-identifier': 7.16.7 - '@babel/template': 7.16.7 - '@babel/traverse': 7.17.12 - '@babel/types': 7.17.12 + '@ampproject/remapping': 2.2.0 + '@babel/code-frame': 7.18.6 + '@babel/generator': 7.20.7 + '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.20.12) + '@babel/helper-module-transforms': 7.20.11 + '@babel/helpers': 7.20.7 + '@babel/parser': 7.20.7 + '@babel/template': 7.20.7 + '@babel/traverse': 7.23.2 + '@babel/types': 7.20.7 + convert-source-map: 1.9.0 + debug: 4.3.4 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.0 transitivePeerDependencies: - supports-color dev: true - /@babel/helper-optimise-call-expression/7.16.7: - resolution: - { - integrity: sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==, - } - engines: { node: '>=6.9.0' } - dependencies: - '@babel/types': 7.17.12 - dev: true - - /@babel/helper-plugin-utils/7.17.12: - resolution: - { - integrity: sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA==, - } - engines: { node: '>=6.9.0' } - dev: true - - /@babel/helper-replace-supers/7.16.7: - resolution: - { - integrity: sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==, - } - engines: { node: '>=6.9.0' } - dependencies: - '@babel/helper-environment-visitor': 7.16.7 - '@babel/helper-member-expression-to-functions': 7.17.7 - '@babel/helper-optimise-call-expression': 7.16.7 - '@babel/traverse': 7.17.12 - '@babel/types': 7.17.12 + /@babel/core@7.24.4: + resolution: {integrity: sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==} + engines: {node: '>=6.9.0'} + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.24.7 + '@babel/helper-compilation-targets': 7.24.7 + '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.4) + '@babel/helpers': 7.24.4 + '@babel/parser': 7.26.9 + '@babel/template': 7.24.7 + '@babel/traverse': 7.24.7 + '@babel/types': 7.26.9 + convert-source-map: 2.0.0 + debug: 4.4.0 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 transitivePeerDependencies: - supports-color dev: true - /@babel/helper-simple-access/7.17.7: - resolution: - { - integrity: sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==, - } - engines: { node: '>=6.9.0' } - dependencies: - '@babel/types': 7.17.12 - dev: true - - /@babel/helper-split-export-declaration/7.16.7: - resolution: - { - integrity: sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==, - } - engines: { node: '>=6.9.0' } + /@babel/generator@7.20.7: + resolution: {integrity: sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==} + engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.17.12 - dev: true - - /@babel/helper-validator-identifier/7.16.7: - resolution: - { - integrity: sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==, - } - engines: { node: '>=6.9.0' } - dev: true - - /@babel/helper-validator-option/7.16.7: - resolution: - { - integrity: sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==, - } - engines: { node: '>=6.9.0' } + '@babel/types': 7.20.7 + '@jridgewell/gen-mapping': 0.3.5 + jsesc: 2.5.2 dev: true - /@babel/helpers/7.17.9: - resolution: - { - integrity: sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q==, - } - engines: { node: '>=6.9.0' } + /@babel/generator@7.23.0: + resolution: {integrity: sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==} + engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.16.7 - '@babel/traverse': 7.17.12 - '@babel/types': 7.17.12 - transitivePeerDependencies: - - supports-color + '@babel/types': 7.23.0 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 2.5.2 dev: true - /@babel/highlight/7.17.12: - resolution: - { - integrity: sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==, - } - engines: { node: '>=6.9.0' } + /@babel/generator@7.24.7: + resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} + engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.16.7 - chalk: 2.4.2 - js-tokens: 4.0.0 + '@babel/types': 7.26.9 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 2.5.2 dev: true - /@babel/parser/7.17.12: - resolution: - { - integrity: sha512-FLzHmN9V3AJIrWfOpvRlZCeVg/WLdicSnTMsLur6uDj9TT8ymUlG9XxURdW/XvuygK+2CW0poOJABdA4m/YKxA==, - } - engines: { node: '>=6.0.0' } - hasBin: true + /@babel/helper-annotate-as-pure@7.18.6: + resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} + engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.17.12 + '@babel/types': 7.24.7 dev: true - /@babel/plugin-syntax-typescript/7.17.12_@babel+core@7.17.12: - resolution: - { - integrity: sha512-TYY0SXFiO31YXtNg3HtFwNJHjLsAyIIhAhNWkQ5whPPS7HWUFlg9z0Ta4qAQNjQbP1wsSt/oKkmZ/4/WWdMUpw==, - } - engines: { node: '>=6.9.0' } + /@babel/helper-compilation-targets@7.20.7(@babel/core@7.20.12): + resolution: {integrity: sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.17.12 - '@babel/helper-plugin-utils': 7.17.12 + '@babel/compat-data': 7.20.10 + '@babel/core': 7.20.12 + '@babel/helper-validator-option': 7.18.6 + browserslist: 4.23.0 + lru-cache: 5.1.1 + semver: 6.3.1 dev: true - /@babel/plugin-transform-typescript/7.17.12_@babel+core@7.17.12: - resolution: - { - integrity: sha512-ICbXZqg6hgenjmwciVI/UfqZtExBrZOrS8sLB5mTHGO/j08Io3MmooULBiijWk9JBknjM3CbbtTc/0ZsqLrjXQ==, - } - engines: { node: '>=6.9.0' } - peerDependencies: - '@babel/core': ^7.0.0-0 + /@babel/helper-compilation-targets@7.24.7: + resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==} + engines: {node: '>=6.9.0'} dependencies: - '@babel/core': 7.17.12 - '@babel/helper-create-class-features-plugin': 7.17.12_@babel+core@7.17.12 - '@babel/helper-plugin-utils': 7.17.12 - '@babel/plugin-syntax-typescript': 7.17.12_@babel+core@7.17.12 - transitivePeerDependencies: - - supports-color + '@babel/compat-data': 7.24.7 + '@babel/helper-validator-option': 7.24.7 + browserslist: 4.23.0 + lru-cache: 5.1.1 + semver: 6.3.1 dev: true - /@babel/preset-typescript/7.17.12_@babel+core@7.17.12: - resolution: - { - integrity: sha512-S1ViF8W2QwAKUGJXxP9NAfNaqGDdEBJKpYkxHf5Yy2C4NPPzXGeR3Lhk7G8xJaaLcFTRfNjVbtbVtm8Gb0mqvg==, - } - engines: { node: '>=6.9.0' } + /@babel/helper-create-class-features-plugin@7.20.12(@babel/core@7.20.12): + resolution: {integrity: sha512-9OunRkbT0JQcednL0UFvbfXpAsUXiGjUk0a7sN8fUXX7Mue79cUSMjHGDRRi/Vz9vYlpIhLV5fMD5dKoMhhsNQ==} + engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.17.12 - '@babel/helper-plugin-utils': 7.17.12 - '@babel/helper-validator-option': 7.16.7 - '@babel/plugin-transform-typescript': 7.17.12_@babel+core@7.17.12 + '@babel/core': 7.20.12 + '@babel/helper-annotate-as-pure': 7.18.6 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.19.0 + '@babel/helper-member-expression-to-functions': 7.20.7 + '@babel/helper-optimise-call-expression': 7.18.6 + '@babel/helper-replace-supers': 7.20.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 + '@babel/helper-split-export-declaration': 7.18.6 transitivePeerDependencies: - supports-color dev: true - /@babel/runtime/7.17.9: - resolution: - { - integrity: sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==, - } - engines: { node: '>=6.9.0' } - dependencies: - regenerator-runtime: 0.13.9 + /@babel/helper-environment-visitor@7.18.9: + resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} + engines: {node: '>=6.9.0'} dev: true - /@babel/template/7.16.7: - resolution: - { - integrity: sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==, - } - engines: { node: '>=6.9.0' } - dependencies: - '@babel/code-frame': 7.16.7 - '@babel/parser': 7.17.12 - '@babel/types': 7.17.12 + /@babel/helper-environment-visitor@7.22.20: + resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} + engines: {node: '>=6.9.0'} dev: true - /@babel/traverse/7.17.12: - resolution: - { - integrity: sha512-zULPs+TbCvOkIFd4FrG53xrpxvCBwLIgo6tO0tJorY7YV2IWFxUfS/lXDJbGgfyYt9ery/Gxj2niwttNnB0gIw==, - } - engines: { node: '>=6.9.0' } + /@babel/helper-environment-visitor@7.24.7: + resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} + engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.16.7 - '@babel/generator': 7.17.12 - '@babel/helper-environment-visitor': 7.16.7 - '@babel/helper-function-name': 7.17.9 - '@babel/helper-hoist-variables': 7.16.7 - '@babel/helper-split-export-declaration': 7.16.7 - '@babel/parser': 7.17.12 - '@babel/types': 7.17.12 - debug: 4.3.4 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color + '@babel/types': 7.26.9 dev: true - /@babel/types/7.17.12: - resolution: - { - integrity: sha512-rH8i29wcZ6x9xjzI5ILHL/yZkbQnCERdHlogKuIb4PUr7do4iT8DPekrTbBLWTnRQm6U0GYABbTMSzijmEqlAg==, - } - engines: { node: '>=6.9.0' } + /@babel/helper-function-name@7.19.0: + resolution: {integrity: sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==} + engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.16.7 - to-fast-properties: 2.0.0 - dev: true - - /@bcoe/v8-coverage/0.2.3: - resolution: - { - integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==, - } + '@babel/template': 7.22.15 + '@babel/types': 7.24.7 dev: true - /@codemirror/commands/6.1.1: - resolution: - { - integrity: sha512-ibDohwkk7vyu3VsnZNlQhwk0OETBtlkYV+6AHfn5Zgq0sxa+yGVX+apwtC3M4wh6AH7yU5si/NysoECs5EGS3Q==, - } + /@babel/helper-function-name@7.23.0: + resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} + engines: {node: '>=6.9.0'} dependencies: - '@codemirror/language': 6.2.1 - '@codemirror/state': 6.1.2 - '@codemirror/view': 6.3.1 - '@lezer/common': 1.0.1 + '@babel/template': 7.22.15 + '@babel/types': 7.23.0 dev: true - /@codemirror/lang-json/6.0.0: - resolution: - { - integrity: sha512-DvTcYTKLmg2viADXlTdufrT334M9jowe1qO02W28nvm+nejcvhM5vot5mE8/kPrxYw/HJHhwu1z2PyBpnMLCNQ==, - } + /@babel/helper-function-name@7.24.7: + resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==} + engines: {node: '>=6.9.0'} dependencies: - '@codemirror/language': 6.2.1 - '@lezer/json': 1.0.0 + '@babel/template': 7.24.7 + '@babel/types': 7.26.9 dev: true - /@codemirror/language/6.2.1: - resolution: - { - integrity: sha512-MC3svxuvIj0MRpFlGHxLS6vPyIdbTr2KKPEW46kCoCXw2ktb4NTkpkPBI/lSP/FoNXLCBJ0mrnUi1OoZxtpW1Q==, - } + /@babel/helper-hoist-variables@7.22.5: + resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} + engines: {node: '>=6.9.0'} dependencies: - '@codemirror/state': 6.1.2 - '@codemirror/view': 6.3.1 - '@lezer/common': 1.0.1 - '@lezer/highlight': 1.1.1 - '@lezer/lr': 1.2.3 - style-mod: 4.0.0 + '@babel/types': 7.23.0 dev: true - /@codemirror/lint/6.0.0: - resolution: - { - integrity: sha512-nUUXcJW1Xp54kNs+a1ToPLK8MadO0rMTnJB8Zk4Z8gBdrN0kqV7uvUraU/T2yqg+grDNR38Vmy/MrhQN/RgwiA==, - } + /@babel/helper-hoist-variables@7.24.7: + resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} + engines: {node: '>=6.9.0'} dependencies: - '@codemirror/state': 6.1.2 - '@codemirror/view': 6.3.1 - crelt: 1.0.5 + '@babel/types': 7.26.9 dev: true - /@codemirror/state/6.1.2: - resolution: - { - integrity: sha512-Mxff85Hp5va+zuj+H748KbubXjrinX/k28lj43H14T2D0+4kuvEFIEIO7hCEcvBT8ubZyIelt9yGOjj2MWOEQA==, - } + /@babel/helper-member-expression-to-functions@7.20.7: + resolution: {integrity: sha512-9J0CxJLq315fEdi4s7xK5TQaNYjZw+nDVpVqr1axNGKzdrdwYBD5b4uKv3n75aABG0rCCTK8Im8Ww7eYfMrZgw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.24.7 dev: true - /@codemirror/theme-one-dark/6.1.0: - resolution: - { - integrity: sha512-AiTHtFRu8+vWT9wWUWDM+cog6ZwgivJogB1Tm/g40NIpLwph7AnmxrSzWfvJN5fBVufsuwBxecQCNmdcR5D7Aw==, - } + /@babel/helper-module-imports@7.18.6: + resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} + engines: {node: '>=6.9.0'} dependencies: - '@codemirror/language': 6.2.1 - '@codemirror/state': 6.1.2 - '@codemirror/view': 6.3.1 - '@lezer/highlight': 1.1.1 + '@babel/types': 7.23.0 dev: true - /@codemirror/view/6.3.1: - resolution: - { - integrity: sha512-NKPBphoV9W2Q6tKXk+ge4q5EhMOOC0rpwdGS80/slNSfsVqkN4gwXIEqSprXJFlf9aUKZU7WhPvqRBMNH+hJkQ==, - } + /@babel/helper-module-imports@7.24.7: + resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} + engines: {node: '>=6.9.0'} dependencies: - '@codemirror/state': 6.1.2 - style-mod: 4.0.0 - w3c-keyname: 2.2.6 + '@babel/traverse': 7.24.7 + '@babel/types': 7.26.9 + transitivePeerDependencies: + - supports-color dev: true - /@colors/colors/1.5.0: - resolution: - { - integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==, - } - engines: { node: '>=0.1.90' } - requiresBuild: true + /@babel/helper-module-transforms@7.20.11: + resolution: {integrity: sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-module-imports': 7.18.6 + '@babel/helper-simple-access': 7.20.2 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/helper-validator-identifier': 7.19.1 + '@babel/template': 7.20.7 + '@babel/traverse': 7.23.2 + '@babel/types': 7.20.7 + transitivePeerDependencies: + - supports-color dev: true - optional: true - /@crownframework/svelte-error-boundary/1.0.3: - resolution: - { - integrity: sha512-ZtfYF9if48Ok8BTx/fn7bERJFKHKO6Ih5d/fkY6/pC3DQEnJq4vy6JOBFcisnpKYcZbjG5F/D4r06kqX7JKyqw==, - } + /@babel/helper-module-transforms@7.24.7(@babel/core@7.24.4): + resolution: {integrity: sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-module-imports': 7.24.7 + '@babel/helper-simple-access': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/helper-validator-identifier': 7.25.9 + transitivePeerDependencies: + - supports-color dev: true - /@cspotcode/source-map-consumer/0.8.0: - resolution: - { - integrity: sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==, - } - engines: { node: '>= 12' } + /@babel/helper-optimise-call-expression@7.18.6: + resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.24.7 dev: true - /@cspotcode/source-map-support/0.7.0: - resolution: - { - integrity: sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==, - } - engines: { node: '>=12' } - dependencies: - '@cspotcode/source-map-consumer': 0.8.0 + /@babel/helper-plugin-utils@7.20.2: + resolution: {integrity: sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==} + engines: {node: '>=6.9.0'} dev: true - /@cypress/request/2.88.10: - resolution: - { - integrity: sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg==, - } - engines: { node: '>= 6' } - dependencies: - aws-sign2: 0.7.0 - aws4: 1.11.0 - caseless: 0.12.0 - combined-stream: 1.0.8 - extend: 3.0.2 - forever-agent: 0.6.1 - form-data: 2.3.3 - http-signature: 1.3.6 - is-typedarray: 1.0.0 - isstream: 0.1.2 - json-stringify-safe: 5.0.1 - mime-types: 2.1.35 - performance-now: 2.1.0 - qs: 6.5.3 - safe-buffer: 5.1.2 - tough-cookie: 2.5.0 - tunnel-agent: 0.6.0 - uuid: 8.3.2 + /@babel/helper-plugin-utils@7.24.7: + resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==} + engines: {node: '>=6.9.0'} dev: true - /@cypress/xvfb/1.2.4_supports-color@8.1.1: - resolution: - { - integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==, - } + /@babel/helper-replace-supers@7.20.7: + resolution: {integrity: sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==} + engines: {node: '>=6.9.0'} dependencies: - debug: 3.2.7_supports-color@8.1.1 - lodash.once: 4.1.1 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-member-expression-to-functions': 7.20.7 + '@babel/helper-optimise-call-expression': 7.18.6 + '@babel/template': 7.22.15 + '@babel/traverse': 7.23.2 + '@babel/types': 7.24.7 transitivePeerDependencies: - supports-color dev: true - /@esbuild/linux-loong64/0.14.54: - resolution: - { - integrity: sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==, - } - engines: { node: '>=12' } - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-loong64/0.15.7: - resolution: - { - integrity: sha512-IKznSJOsVUuyt7cDzzSZyqBEcZe+7WlBqTVXiF1OXP/4Nm387ToaXZ0fyLwI1iBlI/bzpxVq411QE2/Bt2XWWw==, - } - engines: { node: '>=12' } - cpu: [loong64] - os: [linux] - requiresBuild: true + /@babel/helper-simple-access@7.20.2: + resolution: {integrity: sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.0 dev: true - optional: true - /@eslint/eslintrc/1.3.1: - resolution: - { - integrity: sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + /@babel/helper-simple-access@7.24.7: + resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} + engines: {node: '>=6.9.0'} dependencies: - ajv: 6.12.6 - debug: 4.3.4 - espree: 9.4.0 - globals: 13.15.0 - ignore: 5.2.0 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 + '@babel/traverse': 7.24.7 + '@babel/types': 7.26.9 transitivePeerDependencies: - supports-color dev: true - /@grpc/grpc-js/1.6.7: - resolution: - { - integrity: sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw==, - } - engines: { node: ^8.13.0 || >=10.10.0 } + /@babel/helper-skip-transparent-expression-wrappers@7.20.0: + resolution: {integrity: sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==} + engines: {node: '>=6.9.0'} dependencies: - '@grpc/proto-loader': 0.6.12 - '@types/node': 17.0.41 + '@babel/types': 7.24.7 dev: true - /@grpc/proto-loader/0.6.12: - resolution: - { - integrity: sha512-filTVbETFnxb9CyRX98zN18ilChTuf/C5scZ2xyaOTp0EHGq0/ufX8rjqXUcSb1Gpv7eZq4M2jDvbh9BogKnrg==, - } - engines: { node: '>=6' } - hasBin: true + /@babel/helper-split-export-declaration@7.18.6: + resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} + engines: {node: '>=6.9.0'} dependencies: - '@types/long': 4.0.2 - lodash.camelcase: 4.3.0 - long: 4.0.0 - protobufjs: 6.11.3 - yargs: 16.2.0 + '@babel/types': 7.23.0 dev: true - /@histoire/app/0.11.3_vite@3.1.0: - resolution: - { - integrity: sha512-+9+s0tMWWRfjNDaCbB4x2kVtsiGfdLGI1cO6Pnt32MxlUHObYWEmYVRTcVp2Rvj2EA6v5G+bpWHkEmX0jrPpBQ==, - } + /@babel/helper-split-export-declaration@7.22.6: + resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} + engines: {node: '>=6.9.0'} dependencies: - '@histoire/controls': 0.11.3 - '@histoire/shared': 0.11.3_vite@3.1.0 - '@histoire/vendors': 0.11.3 - '@types/flexsearch': 0.7.3 - flexsearch: 0.7.21 - shiki: 0.10.1 - transitivePeerDependencies: - - vite - dev: true - - /@histoire/controls/0.11.3: - resolution: - { - integrity: sha512-6BuLfWE9eGQWDkO9W7C1oAb3W7ZfvWj6nQ8v3gj5yVjNpLZR8vSNkTHKZ6RJiyhroHC+xoMNCQmaD4n+W6NoSg==, - } - dependencies: - '@codemirror/commands': 6.1.1 - '@codemirror/lang-json': 6.0.0 - '@codemirror/language': 6.2.1 - '@codemirror/lint': 6.0.0 - '@codemirror/state': 6.1.2 - '@codemirror/theme-one-dark': 6.1.0 - '@codemirror/view': 6.3.1 - '@histoire/vendors': 0.11.3 - dev: true - - /@histoire/plugin-svelte/0.11.3_y5gzbyoew4hvqk2lyjp3cltjx4: - resolution: - { - integrity: sha512-ph7h0I7/wCnRIsgsIyGBls+N17b7+WVVLyTCfZYSCFuPioEEPMHFO7vVPalM9TS1Ekyy9umCBy3cONbBZYCaWg==, - } - peerDependencies: - histoire: ^0.11.3 - svelte: ^3.0.0 - dependencies: - '@histoire/controls': 0.11.3 - '@histoire/shared': 0.11.3_vite@3.1.0 - '@histoire/vendors': 0.11.3 - histoire: 0.11.3_vite@3.1.0 - svelte: 3.52.0 - transitivePeerDependencies: - - vite + '@babel/types': 7.23.0 dev: true - /@histoire/shared/0.11.3_vite@3.1.0: - resolution: - { - integrity: sha512-kmUKciBuQAE0aY9dk38OL4plfvuWhcaQu9nqFPEIskzm3cuYMVIz1Nm7I3lAkrIgDjdReXobJH5WyPWWu52nrA==, - } - peerDependencies: - vite: ^2.9.0 || ^3.0.0 + /@babel/helper-split-export-declaration@7.24.7: + resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} + engines: {node: '>=6.9.0'} dependencies: - '@types/fs-extra': 9.0.13 - '@types/markdown-it': 12.2.3 - chokidar: 3.5.3 - pathe: 0.2.0 - picocolors: 1.0.0 - vite: 3.1.0 + '@babel/types': 7.26.9 dev: true - /@histoire/vendors/0.11.3: - resolution: - { - integrity: sha512-Cbq3FKQgl/r7+sjuZQKJWdRnMQOzN274zHJv3ueJwCpaPeo0rbUKim3h5w8680j/lIJV9JI72iMtzYcR2VcTTg==, - } + /@babel/helper-string-parser@7.19.4: + resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} + engines: {node: '>=6.9.0'} dev: true - /@humanwhocodes/config-array/0.10.4: - resolution: - { - integrity: sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==, - } - engines: { node: '>=10.10.0' } - dependencies: - '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.4 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color + /@babel/helper-string-parser@7.22.5: + resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} + engines: {node: '>=6.9.0'} dev: true - /@humanwhocodes/gitignore-to-minimatch/1.0.2: - resolution: - { - integrity: sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==, - } + /@babel/helper-string-parser@7.24.7: + resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} + engines: {node: '>=6.9.0'} dev: true - /@humanwhocodes/module-importer/1.0.1: - resolution: - { - integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==, - } - engines: { node: '>=12.22' } + /@babel/helper-string-parser@7.25.9: + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} dev: true - /@humanwhocodes/object-schema/1.2.1: - resolution: - { - integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==, - } + /@babel/helper-validator-identifier@7.19.1: + resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} + engines: {node: '>=6.9.0'} dev: true - /@istanbuljs/schema/0.1.3: - resolution: - { - integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==, - } - engines: { node: '>=8' } + /@babel/helper-validator-identifier@7.22.20: + resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} + engines: {node: '>=6.9.0'} dev: true - /@jridgewell/gen-mapping/0.1.1: - resolution: - { - integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==, - } - engines: { node: '>=6.0.0' } - dependencies: - '@jridgewell/set-array': 1.1.1 - '@jridgewell/sourcemap-codec': 1.4.13 + /@babel/helper-validator-identifier@7.24.7: + resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + engines: {node: '>=6.9.0'} dev: true - /@jridgewell/gen-mapping/0.3.1: - resolution: - { - integrity: sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==, - } - engines: { node: '>=6.0.0' } - dependencies: - '@jridgewell/set-array': 1.1.1 - '@jridgewell/sourcemap-codec': 1.4.13 - '@jridgewell/trace-mapping': 0.3.13 + /@babel/helper-validator-identifier@7.25.9: + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} dev: true - /@jridgewell/resolve-uri/3.0.7: - resolution: - { - integrity: sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==, - } - engines: { node: '>=6.0.0' } + /@babel/helper-validator-option@7.18.6: + resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==} + engines: {node: '>=6.9.0'} dev: true - /@jridgewell/set-array/1.1.1: - resolution: - { - integrity: sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==, - } - engines: { node: '>=6.0.0' } + /@babel/helper-validator-option@7.24.7: + resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} + engines: {node: '>=6.9.0'} dev: true - /@jridgewell/source-map/0.3.2: - resolution: - { - integrity: sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==, - } + /@babel/helpers@7.20.7: + resolution: {integrity: sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==} + engines: {node: '>=6.9.0'} dependencies: - '@jridgewell/gen-mapping': 0.3.1 - '@jridgewell/trace-mapping': 0.3.13 + '@babel/template': 7.20.7 + '@babel/traverse': 7.23.2 + '@babel/types': 7.20.7 + transitivePeerDependencies: + - supports-color dev: true - /@jridgewell/sourcemap-codec/1.4.13: - resolution: - { - integrity: sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==, - } + /@babel/helpers@7.24.4: + resolution: {integrity: sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.24.7 + '@babel/traverse': 7.24.7 + '@babel/types': 7.26.9 + transitivePeerDependencies: + - supports-color dev: true - /@jridgewell/trace-mapping/0.3.13: - resolution: - { - integrity: sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==, - } + /@babel/highlight@7.18.6: + resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} + engines: {node: '>=6.9.0'} dependencies: - '@jridgewell/resolve-uri': 3.0.7 - '@jridgewell/sourcemap-codec': 1.4.13 + '@babel/helper-validator-identifier': 7.22.20 + chalk: 2.4.2 + js-tokens: 4.0.0 dev: true - /@lezer/common/1.0.1: - resolution: - { - integrity: sha512-8TR5++Q/F//tpDsLd5zkrvEX5xxeemafEaek7mUp7Y+bI8cKQXdSqhzTOBaOogETcMOVr0pT3BBPXp13477ciw==, - } + /@babel/highlight@7.22.20: + resolution: {integrity: sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.22.20 + chalk: 2.4.2 + js-tokens: 4.0.0 dev: true - /@lezer/highlight/1.1.1: - resolution: - { - integrity: sha512-duv9D23O9ghEDnnUDmxu+L8pJy4nYo4AbCOHIudUhscrLSazqeJeK1V50EU6ZufWF1zv0KJwu/frFRyZWXxHBQ==, - } + /@babel/highlight@7.24.7: + resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} + engines: {node: '>=6.9.0'} dependencies: - '@lezer/common': 1.0.1 + '@babel/helper-validator-identifier': 7.24.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.1.1 dev: true - /@lezer/json/1.0.0: - resolution: - { - integrity: sha512-zbAuUY09RBzCoCA3lJ1+ypKw5WSNvLqGMtasdW6HvVOqZoCpPr8eWrsGnOVWGKGn8Rh21FnrKRVlJXrGAVUqRw==, - } + /@babel/parser@7.20.7: + resolution: {integrity: sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==} + engines: {node: '>=6.0.0'} + hasBin: true dependencies: - '@lezer/highlight': 1.1.1 - '@lezer/lr': 1.2.3 + '@babel/types': 7.20.7 dev: true - /@lezer/lr/1.2.3: - resolution: - { - integrity: sha512-qpB7rBzH8f6Mzjv2AVZRahcm+2Cf7nbIH++uXbvVOL1yIRvVWQ3HAM/saeBLCyz/togB7LGo76qdJYL1uKQlqA==, - } + /@babel/parser@7.23.0: + resolution: {integrity: sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==} + engines: {node: '>=6.0.0'} + hasBin: true dependencies: - '@lezer/common': 1.0.1 + '@babel/types': 7.23.0 dev: true - /@mapbox/node-pre-gyp/1.0.10: - resolution: - { - integrity: sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==, - } + /@babel/parser@7.24.7: + resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} + engines: {node: '>=6.0.0'} hasBin: true dependencies: - detect-libc: 2.0.1 - https-proxy-agent: 5.0.1 - make-dir: 3.1.0 - node-fetch: 2.6.7 - nopt: 5.0.0 - npmlog: 5.0.1 - rimraf: 3.0.2 - semver: 7.3.7 - tar: 6.1.11 - transitivePeerDependencies: - - encoding - - supports-color + '@babel/types': 7.24.7 dev: true - /@nodelib/fs.scandir/2.1.5: - resolution: - { - integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, - } - engines: { node: '>= 8' } + /@babel/parser@7.26.9: + resolution: {integrity: sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==} + engines: {node: '>=6.0.0'} + hasBin: true dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 + '@babel/types': 7.26.9 dev: true - /@nodelib/fs.stat/2.0.5: - resolution: - { - integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==, - } - engines: { node: '>= 8' } + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.4): + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.7 dev: true - /@nodelib/fs.walk/1.2.8: - resolution: - { - integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==, - } - engines: { node: '>= 8' } + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.4): + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.13.0 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.7 dev: true - /@polka/url/1.0.0-next.21: - resolution: - { - integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==, - } + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.4): + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.7 dev: true - /@protobufjs/aspromise/1.1.2: - resolution: - { - integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==, - } + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.4): + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.7 dev: true - /@protobufjs/base64/1.1.2: - resolution: - { - integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==, - } + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.4): + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.7 dev: true - /@protobufjs/codegen/2.0.4: - resolution: - { - integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==, - } + /@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.7 dev: true - /@protobufjs/eventemitter/1.1.0: - resolution: - { - integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==, - } + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.4): + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.7 dev: true - /@protobufjs/fetch/1.1.0: - resolution: - { - integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==, - } + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.4): + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/inquire': 1.1.0 + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.7 dev: true - /@protobufjs/float/1.0.2: - resolution: - { - integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==, - } + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.4): + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.7 dev: true - /@protobufjs/inquire/1.1.0: - resolution: - { - integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==, - } + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.4): + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.7 dev: true - /@protobufjs/path/1.1.2: - resolution: - { - integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==, - } + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.4): + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.7 dev: true - /@protobufjs/pool/1.1.0: - resolution: - { - integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==, - } + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.4): + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.7 dev: true - /@protobufjs/utf8/1.1.0: - resolution: - { - integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==, - } + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.4): + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.7 dev: true - /@rollup/pluginutils/4.2.1: - resolution: - { - integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==, - } - engines: { node: '>= 8.0.0' } + /@babel/plugin-syntax-typescript@7.20.0(@babel/core@7.20.12): + resolution: {integrity: sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - estree-walker: 2.0.2 - picomatch: 2.3.1 + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 dev: true - /@sveltejs/adapter-vercel/1.0.0-next.77: - resolution: - { - integrity: sha512-r4MqtP+lzx83HfcvI8PU0Yxzmxt6WQq9nzZETLboJouJzhSBUFIN5RmNZfEn6nNIlUwZbGQUEK/FxsRnnxI/Ig==, - } + /@babel/plugin-syntax-typescript@7.24.1(@babel/core@7.24.4): + resolution: {integrity: sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@vercel/nft': 0.22.1 - esbuild: 0.15.7 - transitivePeerDependencies: - - encoding - - supports-color + '@babel/core': 7.24.4 + '@babel/helper-plugin-utils': 7.24.7 dev: true - /@sveltejs/kit/1.0.0-next.405_svelte@3.52.0+vite@3.1.0: - resolution: - { - integrity: sha512-jHSa74F7k+hC+0fof75g/xm/+1M5sM66Qt6v8eLLMSgjkp36Lb5xOioBhbl6w0NYoE5xysLsBWuu+yHytfvCBA==, - } - engines: { node: '>=16.9' } - hasBin: true - requiresBuild: true + /@babel/plugin-transform-typescript@7.20.7(@babel/core@7.20.12): + resolution: {integrity: sha512-m3wVKEvf6SoszD8pu4NZz3PvfKRCMgk6D6d0Qi9hNnlM5M6CFS92EgF4EiHVLKbU0r/r7ty1hg7NPZwE7WRbYw==} + engines: {node: '>=6.9.0'} peerDependencies: - svelte: ^3.44.0 - vite: ^3.0.0 + '@babel/core': ^7.0.0-0 dependencies: - '@sveltejs/vite-plugin-svelte': 1.0.5_svelte@3.52.0+vite@3.1.0 - chokidar: 3.5.3 - sade: 1.8.1 - svelte: 3.52.0 - tiny-glob: 0.2.9 - vite: 3.1.0 + '@babel/core': 7.20.12 + '@babel/helper-create-class-features-plugin': 7.20.12(@babel/core@7.20.12) + '@babel/helper-plugin-utils': 7.20.2 + '@babel/plugin-syntax-typescript': 7.20.0(@babel/core@7.20.12) transitivePeerDependencies: - - diff-match-patch - supports-color dev: true - /@sveltejs/svelte-virtual-list/3.0.1: - resolution: - { - integrity: sha512-aF9TptS7NKKS7/TqpsxQBSDJ9Q0XBYzBehCeIC5DzdMEgrJZpIYao9LRLnyyo6SVodpapm2B7FE/Lj+FSA5/SQ==, - } - dev: false - - /@sveltejs/vite-plugin-svelte/1.0.5_svelte@3.52.0+vite@3.1.0: - resolution: - { - integrity: sha512-CmSdSow0Dr5ua1A11BQMtreWnE0JZmkVIcRU/yG3PKbycKUpXjNdgYTWFSbStLB0vdlGnBbm2+Y4sBVj+C+TIw==, - } - engines: { node: ^14.18.0 || >= 16 } + /@babel/preset-typescript@7.18.6(@babel/core@7.20.12): + resolution: {integrity: sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==} + engines: {node: '>=6.9.0'} peerDependencies: - diff-match-patch: ^1.0.5 - svelte: ^3.44.0 - vite: ^3.0.0 - peerDependenciesMeta: - diff-match-patch: - optional: true + '@babel/core': ^7.0.0-0 dependencies: - '@rollup/pluginutils': 4.2.1 - debug: 4.3.4 - deepmerge: 4.2.2 - kleur: 4.1.5 - magic-string: 0.26.3 - svelte: 3.52.0 - svelte-hmr: 0.14.12_svelte@3.52.0 - vite: 3.1.0 + '@babel/core': 7.20.12 + '@babel/helper-plugin-utils': 7.20.2 + '@babel/helper-validator-option': 7.18.6 + '@babel/plugin-transform-typescript': 7.20.7(@babel/core@7.20.12) transitivePeerDependencies: - supports-color dev: true - /@temporalio/proto/1.4.4: - resolution: - { - integrity: sha512-0pFpwQi0E5ehaiSXmonLYU/SpBw1lUN1irQ8p1/F5a5WjcIguEwo7iLXc98NoOypnlzRoLUaIybOB4Ci+k+kkA==, - } + /@babel/runtime@7.20.7: + resolution: {integrity: sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==} + engines: {node: '>=6.9.0'} dependencies: - long: 5.2.0 - protobufjs: 7.1.2 - dev: true + regenerator-runtime: 0.13.11 + dev: false - /@tootallnate/once/2.0.0: - resolution: - { - integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==, - } - engines: { node: '>= 10' } - dev: true + /@babel/runtime@7.23.2: + resolution: {integrity: sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.14.0 - /@trysound/sax/0.2.0: - resolution: - { - integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==, - } - engines: { node: '>=10.13.0' } + /@babel/template@7.20.7: + resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.18.6 + '@babel/parser': 7.20.7 + '@babel/types': 7.20.7 dev: true - /@tsconfig/node10/1.0.8: - resolution: - { - integrity: sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==, - } + /@babel/template@7.22.15: + resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.22.13 + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 dev: true - /@tsconfig/node12/1.0.9: - resolution: - { - integrity: sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==, - } + /@babel/template@7.24.7: + resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 dev: true - /@tsconfig/node14/1.0.1: - resolution: - { - integrity: sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==, - } + /@babel/traverse@7.23.2: + resolution: {integrity: sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.22.13 + '@babel/generator': 7.23.0 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/parser': 7.23.0 + '@babel/types': 7.23.0 + debug: 4.3.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color dev: true - /@tsconfig/node16/1.0.2: - resolution: - { - integrity: sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==, - } + /@babel/traverse@7.24.7: + resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.24.7 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-function-name': 7.24.7 + '@babel/helper-hoist-variables': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 + debug: 4.4.0 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color dev: true - /@types/babel__core/7.1.19: - resolution: - { - integrity: sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==, - } + /@babel/types@7.20.7: + resolution: {integrity: sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==} + engines: {node: '>=6.9.0'} dependencies: - '@babel/parser': 7.17.12 - '@babel/types': 7.17.12 - '@types/babel__generator': 7.6.4 - '@types/babel__template': 7.4.1 - '@types/babel__traverse': 7.17.1 + '@babel/helper-string-parser': 7.19.4 + '@babel/helper-validator-identifier': 7.19.1 + to-fast-properties: 2.0.0 dev: true - /@types/babel__generator/7.6.4: - resolution: - { - integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==, - } + /@babel/types@7.23.0: + resolution: {integrity: sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==} + engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.17.12 + '@babel/helper-string-parser': 7.22.5 + '@babel/helper-validator-identifier': 7.22.20 + to-fast-properties: 2.0.0 dev: true - /@types/babel__template/7.4.1: - resolution: - { - integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==, - } + /@babel/types@7.24.7: + resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} + engines: {node: '>=6.9.0'} dependencies: - '@babel/parser': 7.17.12 - '@babel/types': 7.17.12 + '@babel/helper-string-parser': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 dev: true - /@types/babel__traverse/7.17.1: - resolution: - { - integrity: sha512-kVzjari1s2YVi77D3w1yuvohV2idweYXMCDzqBiVNN63TcDWrIlTVOYpqVrvbbyOE/IyzBoTKF0fdnLPEORFxA==, - } + /@babel/types@7.26.9: + resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==} + engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.17.12 + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 dev: true - /@types/base-64/1.0.0: - resolution: - { - integrity: sha512-AvCJx/HrfYHmOQRFdVvgKMplXfzTUizmh0tz9GFTpDePWgCY4uoKll84zKlaRoeiYiCr7c9ZnqSTzkl0BUVD6g==, - } + /@bcoe/v8-coverage@0.2.3: + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true - /@types/chai-subset/1.3.3: - resolution: - { - integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==, - } + /@chromatic-com/storybook@3.2.6(react@18.2.0)(storybook@8.6.11): + resolution: {integrity: sha512-FDmn5Ry2DzQdik+eq2sp/kJMMT36Ewe7ONXUXM2Izd97c7r6R/QyGli8eyh/F0iyqVvbLveNYFyF0dBOJNwLqw==} + engines: {node: '>=16.0.0', yarn: '>=1.22.18'} + peerDependencies: + storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 dependencies: - '@types/chai': 4.3.1 + chromatic: 11.22.2 + filesize: 10.1.2 + jsonfile: 6.1.0 + react-confetti: 6.1.0(react@18.2.0) + storybook: 8.6.11(prettier@3.5.3) + strip-ansi: 7.1.0 + transitivePeerDependencies: + - '@chromatic-com/cypress' + - '@chromatic-com/playwright' + - react dev: true - /@types/chai/4.3.1: - resolution: - { - integrity: sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==, - } - dev: true + /@codemirror/autocomplete@6.17.0(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.29.0)(@lezer/common@1.2.3): + resolution: {integrity: sha512-fdfj6e6ZxZf8yrkMHUSJJir7OJkHkZKaOZGzLWIYp2PZ3jd+d+UjG8zVPqJF6d3bKxkhvXTPan/UZ1t7Bqm0gA==} + peerDependencies: + '@codemirror/language': ^6.0.0 + '@codemirror/state': ^6.0.0 + '@codemirror/view': ^6.0.0 + '@lezer/common': ^1.0.0 + dependencies: + '@codemirror/language': 6.10.2 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.29.0 + '@lezer/common': 1.2.3 + dev: false - /@types/concat-stream/1.6.1: - resolution: - { - integrity: sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==, - } + /@codemirror/commands@6.6.0: + resolution: {integrity: sha512-qnY+b7j1UNcTS31Eenuc/5YJB6gQOzkUoNmJQc0rznwqSRpeaWWpjkWy2C/MPTcePpsKJEM26hXrOXl1+nceXg==} dependencies: - '@types/node': 17.0.41 - dev: true + '@codemirror/language': 6.10.2 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.29.0 + '@lezer/common': 1.2.1 + dev: false - /@types/eslint-scope/3.7.3: - resolution: - { - integrity: sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==, - } + /@codemirror/lang-json@6.0.1: + resolution: {integrity: sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==} dependencies: - '@types/eslint': 8.4.3 - '@types/estree': 0.0.51 - dev: true + '@codemirror/language': 6.10.2 + '@lezer/json': 1.0.0 + dev: false - /@types/eslint/8.4.3: - resolution: - { - integrity: sha512-YP1S7YJRMPs+7KZKDb9G63n8YejIwW9BALq7a5j2+H4yl6iOv9CB29edho+cuFRrvmJbbaH2yiVChKLJVysDGw==, - } + /@codemirror/language@6.10.2: + resolution: {integrity: sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==} dependencies: - '@types/estree': 0.0.51 - '@types/json-schema': 7.0.11 - dev: true + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.29.0 + '@lezer/common': 1.2.1 + '@lezer/highlight': 1.1.3 + '@lezer/lr': 1.3.0 + style-mod: 4.0.0 + dev: false - /@types/estree/0.0.51: - resolution: - { - integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==, - } - dev: true + /@codemirror/legacy-modes@6.4.0: + resolution: {integrity: sha512-5m/K+1A6gYR0e+h/dEde7LoGimMjRtWXZFg4Lo70cc8HzjSdHe3fLwjWMR0VRl5KFT1SxalSap7uMgPKF28wBA==} + dependencies: + '@codemirror/language': 6.10.2 + dev: false + + /@codemirror/state@6.4.1: + resolution: {integrity: sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==} + dev: false + + /@codemirror/view@6.29.0: + resolution: {integrity: sha512-ED4ims4fkf7eOA+HYLVP8VVg3NMllt1FPm9PEJBfYFnidKlRITBaua38u68L1F60eNtw2YNcDN5jsIzhKZwWQA==} + dependencies: + '@codemirror/state': 6.4.1 + style-mod: 4.1.2 + w3c-keyname: 2.2.6 + dev: false + + /@csstools/css-parser-algorithms@2.3.1(@csstools/css-tokenizer@2.2.0): + resolution: {integrity: sha512-xrvsmVUtefWMWQsGgFffqWSK03pZ1vfDki4IVIIUxxDKnGBzqNgv0A7SB1oXtVNEkcVO8xi1ZrTL29HhSu5kGA==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + '@csstools/css-tokenizer': ^2.2.0 + dependencies: + '@csstools/css-tokenizer': 2.2.0 + dev: true + + /@csstools/css-tokenizer@2.2.0: + resolution: {integrity: sha512-wErmsWCbsmig8sQKkM6pFhr/oPha1bHfvxsUY5CYSQxwyhA9Ulrs8EqCgClhg4Tgg2XapVstGqSVcz0xOYizZA==} + engines: {node: ^14 || ^16 || >=18} + dev: true + + /@csstools/media-query-list-parser@2.1.4(@csstools/css-parser-algorithms@2.3.1)(@csstools/css-tokenizer@2.2.0): + resolution: {integrity: sha512-V/OUXYX91tAC1CDsiY+HotIcJR+vPtzrX8pCplCpT++i8ThZZsq5F5dzZh/bDM3WUOjrvC1ljed1oSJxMfjqhw==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + '@csstools/css-parser-algorithms': ^2.3.1 + '@csstools/css-tokenizer': ^2.2.0 + dependencies: + '@csstools/css-parser-algorithms': 2.3.1(@csstools/css-tokenizer@2.2.0) + '@csstools/css-tokenizer': 2.2.0 + dev: true + + /@csstools/selector-specificity@3.0.0(postcss-selector-parser@6.0.13): + resolution: {integrity: sha512-hBI9tfBtuPIi885ZsZ32IMEU/5nlZH/KOVYJCOh7gyMxaVLGmLedYqFN6Ui1LXkI8JlC8IsuC0rF0btcRZKd5g==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss-selector-parser: ^6.0.13 + dependencies: + postcss-selector-parser: 6.0.13 + dev: true + + /@esbuild-kit/cjs-loader@2.4.2: + resolution: {integrity: sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==} + dependencies: + '@esbuild-kit/core-utils': 3.1.0 + get-tsconfig: 4.4.0 + dev: true + + /@esbuild-kit/core-utils@3.1.0: + resolution: {integrity: sha512-Uuk8RpCg/7fdHSceR1M6XbSZFSuMrxcePFuGgyvsBn+u339dk5OeL4jv2EojwTN2st/unJGsVm4qHWjWNmJ/tw==} + dependencies: + esbuild: 0.17.19 + source-map-support: 0.5.21 + dev: true + + /@esbuild-kit/esm-loader@2.5.5: + resolution: {integrity: sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw==} + dependencies: + '@esbuild-kit/core-utils': 3.1.0 + get-tsconfig: 4.4.0 + dev: true + + /@esbuild/aix-ppc64@0.19.12: + resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + + /@esbuild/aix-ppc64@0.25.0: + resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + optional: true + + /@esbuild/android-arm64@0.17.19: + resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.19.12: + resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.25.0: + resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + requiresBuild: true + optional: true + + /@esbuild/android-arm@0.17.19: + resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.19.12: + resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.25.0: + resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + requiresBuild: true + optional: true + + /@esbuild/android-x64@0.17.19: + resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.19.12: + resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.25.0: + resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + requiresBuild: true + optional: true + + /@esbuild/darwin-arm64@0.17.19: + resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.19.12: + resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.25.0: + resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optional: true + + /@esbuild/darwin-x64@0.17.19: + resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.19.12: + resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.25.0: + resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + requiresBuild: true + optional: true + + /@esbuild/freebsd-arm64@0.17.19: + resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.19.12: + resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.25.0: + resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + optional: true + + /@esbuild/freebsd-x64@0.17.19: + resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.19.12: + resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.25.0: + resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + optional: true + + /@esbuild/linux-arm64@0.17.19: + resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.19.12: + resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.25.0: + resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-arm@0.17.19: + resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.19.12: + resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.25.0: + resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-ia32@0.17.19: + resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.19.12: + resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.25.0: + resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-loong64@0.17.19: + resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.19.12: + resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.25.0: + resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-mips64el@0.17.19: + resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.19.12: + resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.25.0: + resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-ppc64@0.17.19: + resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.19.12: + resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.25.0: + resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-riscv64@0.17.19: + resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.19.12: + resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.25.0: + resolution: {integrity: sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-s390x@0.17.19: + resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.19.12: + resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.25.0: + resolution: {integrity: sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/linux-x64@0.17.19: + resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.19.12: + resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.25.0: + resolution: {integrity: sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /@esbuild/netbsd-arm64@0.25.0: + resolution: {integrity: sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + requiresBuild: true + optional: true + + /@esbuild/netbsd-x64@0.17.19: + resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.19.12: + resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.25.0: + resolution: {integrity: sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + optional: true + + /@esbuild/openbsd-arm64@0.25.0: + resolution: {integrity: sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + requiresBuild: true + optional: true + + /@esbuild/openbsd-x64@0.17.19: + resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.19.12: + resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.25.0: + resolution: {integrity: sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + optional: true + + /@esbuild/sunos-x64@0.17.19: + resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.19.12: + resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.25.0: + resolution: {integrity: sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + requiresBuild: true + optional: true + + /@esbuild/win32-arm64@0.17.19: + resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.19.12: + resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.25.0: + resolution: {integrity: sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + requiresBuild: true + optional: true + + /@esbuild/win32-ia32@0.17.19: + resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.19.12: + resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.25.0: + resolution: {integrity: sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + requiresBuild: true + optional: true + + /@esbuild/win32-x64@0.17.19: + resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.19.12: + resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.25.0: + resolution: {integrity: sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + requiresBuild: true + optional: true + + /@eslint-community/eslint-utils@4.4.0(eslint@8.47.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.47.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /@eslint-community/regexpp@4.7.0: + resolution: {integrity: sha512-+HencqxU7CFJnQb7IKtuNBqS6Yx3Tz4kOL8BJXo+JyeiBm5MEX6pO8onXDkjrkCRlfYXS1Axro15ZjVFe9YgsA==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true + + /@eslint/eslintrc@1.4.1: + resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.4.0 + espree: 9.6.1 + globals: 13.21.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@eslint/eslintrc@2.1.2: + resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.6.1 + globals: 13.21.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@eslint/js@8.47.0: + resolution: {integrity: sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@exodus/schemasafe@1.3.0: + resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} + requiresBuild: true + dev: false + optional: true + + /@fontsource-variable/inter@5.0.8: + resolution: {integrity: sha512-WkYfFNccmEIeL2fNg0mYeLWqOoB7xD8MFxFRc4IwbSP2o8ZaBt36v5aW4by4MyrgGRMNk7uNi5LbvYKq6clPjw==} + dev: false + + /@fontsource/noto-sans-mono@5.0.9: + resolution: {integrity: sha512-kEnRN/Vs7TgWkY+Y5U36b2NZOYITWEY1T6LYkjFopCA2eaUWY4DDO7UOov55VE/PHDZH8KZZIOoEmBiM4Nv7Kw==} + dev: false + + /@gcornut/valibot-json-schema@0.42.0(esbuild@0.25.0)(typescript@5.2.2): + resolution: {integrity: sha512-4Et4AN6wmqeA0PfU5Clkv/IS27wiefsWf6TemAZrb75uzkClYEFavim7SboeKwbll9Nbsn2Iv0LT/HS5H7orZg==} + hasBin: true + requiresBuild: true + dependencies: + valibot: 0.42.1(typescript@5.2.2) + optionalDependencies: + '@types/json-schema': 7.0.15 + esbuild-runner: 2.2.2(esbuild@0.25.0) + transitivePeerDependencies: + - esbuild + - typescript + dev: false + optional: true + + /@grpc/grpc-js@1.10.9: + resolution: {integrity: sha512-5tcgUctCG0qoNyfChZifz2tJqbRbXVO9J7X6duFcOjY3HUNCxg5D0ZCK7EP9vIcZ0zRpLU9bWkyCqVCLZ46IbQ==} + engines: {node: '>=12.10.0'} + dependencies: + '@grpc/proto-loader': 0.7.13 + '@js-sdsl/ordered-map': 4.4.2 + dev: true + + /@grpc/proto-loader@0.7.13: + resolution: {integrity: sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==} + engines: {node: '>=6'} + hasBin: true + dependencies: + lodash.camelcase: 4.3.0 + long: 5.2.3 + protobufjs: 7.2.5 + yargs: 17.7.2 + dev: true + + /@hapi/hoek@9.3.0: + resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + + /@hapi/topo@5.1.0: + resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + dependencies: + '@hapi/hoek': 9.3.0 + + /@humanwhocodes/config-array@0.11.10: + resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/config-array@0.9.5: + resolution: {integrity: sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/module-importer@1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true + + /@humanwhocodes/object-schema@1.2.1: + resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + dev: true + + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: true + + /@istanbuljs/load-nyc-config@1.1.0: + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + dev: true + + /@istanbuljs/schema@0.1.3: + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + dev: true + + /@jest/console@29.7.0: + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@types/node': 18.15.3 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + dev: true + + /@jest/core@29.7.0: + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.15.3 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@18.15.3) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.5 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + dev: true + + /@jest/create-cache-key-function@29.7.0: + resolution: {integrity: sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + dev: true + + /@jest/environment@29.7.0: + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.15.3 + jest-mock: 29.7.0 + dev: true + + /@jest/expect-utils@29.7.0: + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-get-type: 29.6.3 + dev: true + + /@jest/expect@29.7.0: + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/fake-timers@29.7.0: + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 18.15.3 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + dev: true + + /@jest/globals@29.7.0: + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/reporters@29.7.0: + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + '@types/node': 18.15.3 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.7 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.0.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + + /@jest/source-map@29.6.3: + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + callsites: 3.1.0 + graceful-fs: 4.2.11 + dev: true + + /@jest/test-result@29.7.0: + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.4 + collect-v8-coverage: 1.0.2 + dev: true + + /@jest/test-sequencer@29.7.0: + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + dev: true + + /@jest/transform@29.7.0: + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/core': 7.24.4 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.5 + pirates: 4.0.6 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@jest/types@29.6.3: + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 3.0.4 + '@types/node': 18.15.3 + '@types/yargs': 17.0.24 + chalk: 4.1.2 + dev: true + + /@jridgewell/gen-mapping@0.1.1: + resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + dev: true + + /@jridgewell/gen-mapping@0.3.5: + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + /@jridgewell/resolve-uri@3.1.0: + resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} + engines: {node: '>=6.0.0'} + + /@jridgewell/set-array@1.2.1: + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + /@jridgewell/source-map@0.3.5: + resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==} + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + dev: true + + /@jridgewell/sourcemap-codec@1.5.0: + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + /@jridgewell/trace-mapping@0.3.25: + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.5.0 + + /@js-sdsl/ordered-map@4.4.2: + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + dev: true + + /@lezer/common@1.0.2: + resolution: {integrity: sha512-SVgiGtMnMnW3ActR8SXgsDhw7a0w0ChHSYAyAUxxrOiJ1OqYWEKk/xJd84tTSPo1mo6DXLObAJALNnd0Hrv7Ng==} + dev: false + + /@lezer/common@1.2.1: + resolution: {integrity: sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==} + dev: false + + /@lezer/common@1.2.3: + resolution: {integrity: sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==} + dev: false + + /@lezer/highlight@1.1.3: + resolution: {integrity: sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==} + dependencies: + '@lezer/common': 1.0.2 + dev: false + + /@lezer/json@1.0.0: + resolution: {integrity: sha512-zbAuUY09RBzCoCA3lJ1+ypKw5WSNvLqGMtasdW6HvVOqZoCpPr8eWrsGnOVWGKGn8Rh21FnrKRVlJXrGAVUqRw==} + dependencies: + '@lezer/highlight': 1.1.3 + '@lezer/lr': 1.3.0 + dev: false + + /@lezer/lr@1.3.0: + resolution: {integrity: sha512-rpvS+WPS/PlbJCiW+bzXPbIFIRXmzRiTEDzMvrvgpED05w5ZQO59AzH3BJen2AnHuJIlP3DcJRjsKLTrkknUNA==} + dependencies: + '@lezer/common': 1.2.1 + dev: false + + /@mapbox/node-pre-gyp@1.0.10: + resolution: {integrity: sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==} + hasBin: true + dependencies: + detect-libc: 2.0.1 + https-proxy-agent: 5.0.1 + make-dir: 3.1.0 + node-fetch: 2.6.8 + nopt: 5.0.0 + npmlog: 5.0.1 + rimraf: 3.0.2 + semver: 7.5.4 + tar: 6.2.0 + transitivePeerDependencies: + - encoding + - supports-color + dev: true + + /@mdx-js/react@3.0.1(@types/react@19.1.2)(react@18.2.0): + resolution: {integrity: sha512-9ZrPIU4MGf6et1m1ov3zKf+q9+deetI51zprKB1D/z3NOb+rUxxtEl3mCjW5wTGh6VhRdwPueh1oRzi6ezkA8A==} + peerDependencies: + '@types/react': '>=16' + react: '>=16' + dependencies: + '@types/mdx': 2.0.9 + '@types/react': 19.1.2 + react: 18.2.0 + dev: true + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + dev: true + + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + dev: true + optional: true + + /@playwright/test@1.49.1: + resolution: {integrity: sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==} + engines: {node: '>=18'} + hasBin: true + dependencies: + playwright: 1.49.1 + dev: true + + /@polka/url@1.0.0-next.24: + resolution: {integrity: sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==} + + /@poppinss/macroable@1.0.4: + resolution: {integrity: sha512-ct43jurbe7lsUX5eIrj4ijO3j/6zIPp7CDnFWXDs7UPAbw1Pu1iH3oAmFdP4jcskKJBURH5M9oTtyeiUXyHX8Q==} + engines: {node: '>=18.16.0'} + requiresBuild: true + dev: false + optional: true + + /@protobufjs/aspromise@1.1.2: + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + dev: true + + /@protobufjs/base64@1.1.2: + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + dev: true + + /@protobufjs/codegen@2.0.4: + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + dev: true + + /@protobufjs/eventemitter@1.1.0: + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + dev: true + + /@protobufjs/fetch@1.1.0: + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + dev: true + + /@protobufjs/float@1.0.2: + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + dev: true + + /@protobufjs/inquire@1.1.0: + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + dev: true + + /@protobufjs/path@1.1.2: + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + dev: true + + /@protobufjs/pool@1.1.0: + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + dev: true + + /@protobufjs/utf8@1.1.0: + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + dev: true + + /@rollup/pluginutils@4.2.1: + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} + engines: {node: '>= 8.0.0'} + dependencies: + estree-walker: 2.0.2 + picomatch: 2.3.1 + dev: true + + /@rollup/rollup-android-arm-eabi@4.40.1: + resolution: {integrity: sha512-kxz0YeeCrRUHz3zyqvd7n+TVRlNyTifBsmnmNPtk3hQURUyG9eAB+usz6DAwagMusjx/zb3AjvDUvhFGDAexGw==} + cpu: [arm] + os: [android] + requiresBuild: true + optional: true + + /@rollup/rollup-android-arm64@4.40.1: + resolution: {integrity: sha512-PPkxTOisoNC6TpnDKatjKkjRMsdaWIhyuMkA4UsBXT9WEZY4uHezBTjs6Vl4PbqQQeu6oION1w2voYZv9yquCw==} + cpu: [arm64] + os: [android] + requiresBuild: true + optional: true + + /@rollup/rollup-darwin-arm64@4.40.1: + resolution: {integrity: sha512-VWXGISWFY18v/0JyNUy4A46KCFCb9NVsH+1100XP31lud+TzlezBbz24CYzbnA4x6w4hx+NYCXDfnvDVO6lcAA==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optional: true + + /@rollup/rollup-darwin-x64@4.40.1: + resolution: {integrity: sha512-nIwkXafAI1/QCS7pxSpv/ZtFW6TXcNUEHAIA9EIyw5OzxJZQ1YDrX+CL6JAIQgZ33CInl1R6mHet9Y/UZTg2Bw==} + cpu: [x64] + os: [darwin] + requiresBuild: true + optional: true + + /@rollup/rollup-freebsd-arm64@4.40.1: + resolution: {integrity: sha512-BdrLJ2mHTrIYdaS2I99mriyJfGGenSaP+UwGi1kB9BLOCu9SR8ZpbkmmalKIALnRw24kM7qCN0IOm6L0S44iWw==} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + optional: true + + /@rollup/rollup-freebsd-x64@4.40.1: + resolution: {integrity: sha512-VXeo/puqvCG8JBPNZXZf5Dqq7BzElNJzHRRw3vjBE27WujdzuOPecDPc/+1DcdcTptNBep3861jNq0mYkT8Z6Q==} + cpu: [x64] + os: [freebsd] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.40.1: + resolution: {integrity: sha512-ehSKrewwsESPt1TgSE/na9nIhWCosfGSFqv7vwEtjyAqZcvbGIg4JAcV7ZEh2tfj/IlfBeZjgOXm35iOOjadcg==} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm-musleabihf@4.40.1: + resolution: {integrity: sha512-m39iO/aaurh5FVIu/F4/Zsl8xppd76S4qoID8E+dSRQvTyZTOI2gVk3T4oqzfq1PtcvOfAVlwLMK3KRQMaR8lg==} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.40.1: + resolution: {integrity: sha512-Y+GHnGaku4aVLSgrT0uWe2o2Rq8te9hi+MwqGF9r9ORgXhmHK5Q71N757u0F8yU1OIwUIFy6YiJtKjtyktk5hg==} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.40.1: + resolution: {integrity: sha512-jEwjn3jCA+tQGswK3aEWcD09/7M5wGwc6+flhva7dsQNRZZTe30vkalgIzV4tjkopsTS9Jd7Y1Bsj6a4lzz8gQ==} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-loongarch64-gnu@4.40.1: + resolution: {integrity: sha512-ySyWikVhNzv+BV/IDCsrraOAZ3UaC8SZB67FZlqVwXwnFhPihOso9rPOxzZbjp81suB1O2Topw+6Ug3JNegejQ==} + cpu: [loong64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-powerpc64le-gnu@4.40.1: + resolution: {integrity: sha512-BvvA64QxZlh7WZWqDPPdt0GH4bznuL6uOO1pmgPnnv86rpUpc8ZxgZwcEgXvo02GRIZX1hQ0j0pAnhwkhwPqWg==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.40.1: + resolution: {integrity: sha512-EQSP+8+1VuSulm9RKSMKitTav89fKbHymTf25n5+Yr6gAPZxYWpj3DzAsQqoaHAk9YX2lwEyAf9S4W8F4l3VBQ==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-riscv64-musl@4.40.1: + resolution: {integrity: sha512-n/vQ4xRZXKuIpqukkMXZt9RWdl+2zgGNx7Uda8NtmLJ06NL8jiHxUawbwC+hdSq1rrw/9CghCpEONor+l1e2gA==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-s390x-gnu@4.40.1: + resolution: {integrity: sha512-h8d28xzYb98fMQKUz0w2fMc1XuGzLLjdyxVIbhbil4ELfk5/orZlSTpF/xdI9C8K0I8lCkq+1En2RJsawZekkg==} + cpu: [s390x] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.40.1: + resolution: {integrity: sha512-XiK5z70PEFEFqcNj3/zRSz/qX4bp4QIraTy9QjwJAb/Z8GM7kVUsD0Uk8maIPeTyPCP03ChdI+VVmJriKYbRHQ==} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.40.1: + resolution: {integrity: sha512-2BRORitq5rQ4Da9blVovzNCMaUlyKrzMSvkVR0D4qPuOy/+pMCrh1d7o01RATwVy+6Fa1WBw+da7QPeLWU/1mQ==} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.40.1: + resolution: {integrity: sha512-b2bcNm9Kbde03H+q+Jjw9tSfhYkzrDUf2d5MAd1bOJuVplXvFhWz7tRtWvD8/ORZi7qSCy0idW6tf2HgxSXQSg==} + cpu: [arm64] + os: [win32] + requiresBuild: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.40.1: + resolution: {integrity: sha512-DfcogW8N7Zg7llVEfpqWMZcaErKfsj9VvmfSyRjCyo4BI3wPEfrzTtJkZG6gKP/Z92wFm6rz2aDO7/JfiR/whA==} + cpu: [ia32] + os: [win32] + requiresBuild: true + optional: true + + /@rollup/rollup-win32-x64-msvc@4.40.1: + resolution: {integrity: sha512-ECyOuDeH3C1I8jH2MK1RtBJW+YPMvSfT0a5NN0nHfQYnDSJ6tUiZH3gzwVP5/Kfh/+Tt7tpWVF9LXNTnhTJ3kA==} + cpu: [x64] + os: [win32] + requiresBuild: true + optional: true + + /@sideway/address@4.1.5: + resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} + dependencies: + '@hapi/hoek': 9.3.0 + + /@sideway/formula@3.0.1: + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + + /@sideway/pinpoint@2.0.0: + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + + /@sinclair/typebox@0.34.37: + resolution: {integrity: sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==} + requiresBuild: true + dev: false + optional: true + + /@sinonjs/commons@3.0.1: + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + dependencies: + type-detect: 4.0.8 + dev: true + + /@sinonjs/fake-timers@10.3.0: + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + dependencies: + '@sinonjs/commons': 3.0.1 + dev: true + + /@standard-schema/spec@1.0.0: + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + requiresBuild: true + dev: false + optional: true + + /@storybook/addon-a11y@8.6.11(storybook@8.6.11): + resolution: {integrity: sha512-VijoBrsoHkZDkBNT9Fkpt569S4p5rVRBCUrcyZiGOLAt/DZtuAWQEBO4eENHPldvEXtjHckZRzz4oaUwJGcXGQ==} + peerDependencies: + storybook: ^8.6.11 + dependencies: + '@storybook/addon-highlight': 8.6.11(storybook@8.6.11) + '@storybook/global': 5.0.0 + '@storybook/test': 8.6.11(storybook@8.6.11) + axe-core: 4.10.2 + storybook: 8.6.11(prettier@3.5.3) + dev: true + + /@storybook/addon-actions@8.6.11(storybook@8.6.11): + resolution: {integrity: sha512-7VORphqmxJf2J2a8tRgCAIeaMQ1JjYZwmwsrOwXBt77NjJbAC2qx9KecN1fsKKCyRVSk/wAICvOLStKM15+v2g==} + peerDependencies: + storybook: ^8.6.11 + dependencies: + '@storybook/global': 5.0.0 + '@types/uuid': 9.0.8 + dequal: 2.0.3 + polished: 4.2.2 + storybook: 8.6.11(prettier@3.5.3) + uuid: 9.0.1 + dev: true + + /@storybook/addon-actions@8.6.4(storybook@8.6.11): + resolution: {integrity: sha512-mCcyfkeb19fJX0dpQqqZCnWBwjVn0/27xcpR0mbm/KW2wTByU6bKFFujgrHsX3ONl97IcIaUnmwwUwBr1ebZXw==} + peerDependencies: + storybook: ^8.6.4 + dependencies: + '@storybook/global': 5.0.0 + '@types/uuid': 9.0.8 + dequal: 2.0.3 + polished: 4.2.2 + storybook: 8.6.11(prettier@3.5.3) + uuid: 9.0.1 + dev: true + + /@storybook/addon-backgrounds@8.6.11(storybook@8.6.11): + resolution: {integrity: sha512-1P6qqYOQKbidV0VzYOgc7upjHqIQaFogbqm/DqNyPnwlxTIuqtzkFLiZQxMmGSQekA9SB/7ZfGglFByQXgrIUA==} + peerDependencies: + storybook: ^8.6.11 + dependencies: + '@storybook/global': 5.0.0 + memoizerific: 1.11.3 + storybook: 8.6.11(prettier@3.5.3) + ts-dedent: 2.2.0 + dev: true + + /@storybook/addon-controls@8.6.11(storybook@8.6.11): + resolution: {integrity: sha512-CpXu4HhiyRBikygMskmFhfgKtyz2/3BWTzpg6OrmaYuoEnxP+RMPeXZQxCW9pbH8ewGZlvX++VEt1Cletd95zQ==} + peerDependencies: + storybook: ^8.6.11 + dependencies: + '@storybook/global': 5.0.0 + dequal: 2.0.3 + storybook: 8.6.11(prettier@3.5.3) + ts-dedent: 2.2.0 + dev: true + + /@storybook/addon-docs@8.6.11(@types/react@19.1.2)(storybook@8.6.11): + resolution: {integrity: sha512-gTSF1m3HJkeU7GKPYhe8grO48FbpulvIWZ213PtnWedgVkTNieok3oBmmigv17ua/QXH0u5EbaoMSxaAyrsAzg==} + peerDependencies: + storybook: ^8.6.11 + dependencies: + '@mdx-js/react': 3.0.1(@types/react@19.1.2)(react@18.2.0) + '@storybook/blocks': 8.6.11(react-dom@18.2.0)(react@18.2.0)(storybook@8.6.11) + '@storybook/csf-plugin': 8.6.11(storybook@8.6.11) + '@storybook/react-dom-shim': 8.6.11(react-dom@18.2.0)(react@18.2.0)(storybook@8.6.11) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + storybook: 8.6.11(prettier@3.5.3) + ts-dedent: 2.2.0 + transitivePeerDependencies: + - '@types/react' + dev: true + + /@storybook/addon-essentials@8.6.11(@types/react@19.1.2)(storybook@8.6.11): + resolution: {integrity: sha512-QII5yTM0cGRryfTSJSK5Hf2CEiAX3atqccHZbPipkqO7dE9YDBvRfVwG0cQpHdv10tP066MDWgVDF5E3pDKecw==} + peerDependencies: + storybook: ^8.6.11 + dependencies: + '@storybook/addon-actions': 8.6.11(storybook@8.6.11) + '@storybook/addon-backgrounds': 8.6.11(storybook@8.6.11) + '@storybook/addon-controls': 8.6.11(storybook@8.6.11) + '@storybook/addon-docs': 8.6.11(@types/react@19.1.2)(storybook@8.6.11) + '@storybook/addon-highlight': 8.6.11(storybook@8.6.11) + '@storybook/addon-measure': 8.6.11(storybook@8.6.11) + '@storybook/addon-outline': 8.6.11(storybook@8.6.11) + '@storybook/addon-toolbars': 8.6.11(storybook@8.6.11) + '@storybook/addon-viewport': 8.6.11(storybook@8.6.11) + storybook: 8.6.11(prettier@3.5.3) + ts-dedent: 2.2.0 + transitivePeerDependencies: + - '@types/react' + dev: true + + /@storybook/addon-highlight@8.6.11(storybook@8.6.11): + resolution: {integrity: sha512-xiNIQVDj34PE6xv+V6z8dOi5HrfQYm9FE4okOOSfYcvL8p+3exAs1+13TIsDtf/c1wy8/IbU4S+H8XUExmI6qg==} + peerDependencies: + storybook: ^8.6.11 + dependencies: + '@storybook/global': 5.0.0 + storybook: 8.6.11(prettier@3.5.3) + dev: true + + /@storybook/addon-interactions@8.6.11(storybook@8.6.11): + resolution: {integrity: sha512-j9wUf8lQF922eC2HbWG4LdeflOu/togry80QB+Sqxs4+dg4iWhjxmN8gWav14/oa0vJBLN7Z2Tr4ifsJUEwRkg==} + peerDependencies: + storybook: ^8.6.11 + dependencies: + '@storybook/global': 5.0.0 + '@storybook/instrumenter': 8.6.11(storybook@8.6.11) + '@storybook/test': 8.6.11(storybook@8.6.11) + polished: 4.2.2 + storybook: 8.6.11(prettier@3.5.3) + ts-dedent: 2.2.0 + dev: true + + /@storybook/addon-links@8.6.11(react@18.2.0)(storybook@8.6.11): + resolution: {integrity: sha512-1srydJWDiXUkYCbhibvZT3IqzMMiTR1hbC4UYPpvh3vT3G3NcZT17OsIyl/F/cYPWnjtYHZ5RI83h7dm0qK5cg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta + storybook: ^8.6.11 + peerDependenciesMeta: + react: + optional: true + dependencies: + '@storybook/global': 5.0.0 + react: 18.2.0 + storybook: 8.6.11(prettier@3.5.3) + ts-dedent: 2.2.0 + dev: true + + /@storybook/addon-measure@8.6.11(storybook@8.6.11): + resolution: {integrity: sha512-9Y0qXF9WEg4WHszT17FhppHXcRLv+XC+kpLiKYiL2D4Cm1Xz/2s0N1J50dLBL/gHW04jrVk5lPlpJbcJgWBE5Q==} + peerDependencies: + storybook: ^8.6.11 + dependencies: + '@storybook/global': 5.0.0 + storybook: 8.6.11(prettier@3.5.3) + tiny-invariant: 1.3.3 + dev: true + + /@storybook/addon-outline@8.6.11(storybook@8.6.11): + resolution: {integrity: sha512-LddoqoWYQArzxFXEGL2omJFRCfYn6/F+4IkPuQC+S7wZrwBw89riVPKjL8EmAZ62pEByhJHabUD8ZXTVTqpMlg==} + peerDependencies: + storybook: ^8.6.11 + dependencies: + '@storybook/global': 5.0.0 + storybook: 8.6.11(prettier@3.5.3) + ts-dedent: 2.2.0 + dev: true + + /@storybook/addon-svelte-csf@5.0.0-next.27(@storybook/svelte@8.6.11)(@sveltejs/vite-plugin-svelte@5.0.3)(storybook@8.6.11)(svelte@5.25.5)(vite@6.2.7): + resolution: {integrity: sha512-iWEg084pWy2m9Z7jLDg+zXS8cjA2eRBrLe127KV9P1RYtyYzh45ALN72wVNoHmWzLNQl2CAh0ggTCKjMHHEjRg==} + peerDependencies: + '@storybook/svelte': ^0.0.0-0 || ^8.2.0 || ^9.0.0-0 + '@sveltejs/vite-plugin-svelte': ^4.0.0 || ^5.0.0 + storybook: ^0.0.0-0 || ^8.2.0 || ^9.0.0-0 + svelte: ^5.0.0 + vite: ^5.0.0 || ^6.0.0 + dependencies: + '@storybook/csf': 0.1.13 + '@storybook/svelte': 8.6.11(storybook@8.6.11)(svelte@5.25.5) + '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.25.5)(vite@6.2.7) + dedent: 1.5.3 + es-toolkit: 1.32.0 + esrap: 1.4.5 + magic-string: 0.30.17 + storybook: 8.6.11(prettier@3.5.3) + svelte: 5.25.5 + svelte-ast-print: 0.4.2(svelte@5.25.5) + vite: 6.2.7(@types/node@18.15.3) + zimmerframe: 1.1.2 + transitivePeerDependencies: + - babel-plugin-macros + dev: true + + /@storybook/addon-themes@8.6.11(storybook@8.6.11): + resolution: {integrity: sha512-+Prr/az40J2bkEEEVafA2Yl1hS4a45mpJ3f/ob0takj7uV5j/l9LnsB3B5IKsZhOddbAhRiftxxgiSBULDuJ9Q==} + peerDependencies: + storybook: ^8.6.11 + dependencies: + storybook: 8.6.11(prettier@3.5.3) + ts-dedent: 2.2.0 + dev: true + + /@storybook/addon-toolbars@8.6.11(storybook@8.6.11): + resolution: {integrity: sha512-5ImZfjXisBhYWPoxjYr08nucCF6wqq1a81nWdSnHaB1xFKJZAKtp3GiF7Hyp8B0+olMk1OgSJXEXlXZ1ZbK5Vg==} + peerDependencies: + storybook: ^8.6.11 + dependencies: + storybook: 8.6.11(prettier@3.5.3) + dev: true + + /@storybook/addon-viewport@8.6.11(storybook@8.6.11): + resolution: {integrity: sha512-EdzrkUyMvm0epYkUspBF5osU0rIHglD1+YK84C8ibJjx3JpnpLFaDpecwjFaFgxjQQVveHKatYcHpz09aEywqQ==} + peerDependencies: + storybook: ^8.6.11 + dependencies: + memoizerific: 1.11.3 + storybook: 8.6.11(prettier@3.5.3) + dev: true + + /@storybook/blocks@8.6.11(react-dom@18.2.0)(react@18.2.0)(storybook@8.6.11): + resolution: {integrity: sha512-2IyS3nB6SoEjIt0nWUMxtRIjJRUvDU8EF/eMbM4F/FuwIM402P3kNQ4n4+q1xZtYjvoMr5QUq+K8YF4ZlxIz0A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^8.6.11 + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + dependencies: + '@storybook/icons': 1.4.0(react-dom@18.2.0)(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + storybook: 8.6.11(prettier@3.5.3) + ts-dedent: 2.2.0 + dev: true + + /@storybook/builder-vite@8.6.11(storybook@8.6.11)(vite@6.2.7): + resolution: {integrity: sha512-d8SsHr6iM49kTyrg6PTYn9aHxOBiWUZvPhOOtfODHl2SH9PPRenfwX3a3B+OsD04GPVGvLz5fp4Apg9lrDVSzw==} + peerDependencies: + storybook: ^8.6.11 + vite: ^4.0.0 || ^5.0.0 || ^6.0.0 + dependencies: + '@storybook/csf-plugin': 8.6.11(storybook@8.6.11) + browser-assert: 1.2.1 + storybook: 8.6.11(prettier@3.5.3) + ts-dedent: 2.2.0 + vite: 6.2.7(@types/node@18.15.3) + dev: true + + /@storybook/components@8.6.11(storybook@8.6.11): + resolution: {integrity: sha512-+lHcwQsSO8usKTXIBBmgmRCAa0L+KQaLJ5ARqkRTm6OjzkVVS+EPnIgL4H1nqzbwiTVXxSSOwAk+rST83KICnA==} + peerDependencies: + storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 + dependencies: + storybook: 8.6.11(prettier@3.5.3) + dev: true + + /@storybook/core@8.6.11(prettier@3.5.3)(storybook@8.6.11): + resolution: {integrity: sha512-fhzLQ9HpljbLpkHykafmcjIERHI5j6SZhylFCDwEWkETuZtRbyCs3mmULutcEOzKhxRgNtiIRoRmZPdQcPtHNg==} + peerDependencies: + prettier: ^2 || ^3 + peerDependenciesMeta: + prettier: + optional: true + dependencies: + '@storybook/theming': 8.6.11(storybook@8.6.11) + better-opn: 3.0.2 + browser-assert: 1.2.1 + esbuild: 0.25.0 + esbuild-register: 3.5.0(esbuild@0.25.0) + jsdoc-type-pratt-parser: 4.1.0 + prettier: 3.5.3 + process: 0.11.10 + recast: 0.23.6 + semver: 7.6.3 + util: 0.12.5 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - storybook + - supports-color + - utf-8-validate + dev: true + + /@storybook/csf-plugin@8.6.11(storybook@8.6.11): + resolution: {integrity: sha512-NC/o+SSjSC29hcQrPNoLCDRkqRTagkcAgFf0xEybX3mkLT0q+w3ZHJg1HQz7TtaigIzZ06iIPncif2xKvYgETw==} + peerDependencies: + storybook: ^8.6.11 + dependencies: + storybook: 8.6.11(prettier@3.5.3) + unplugin: 1.5.0 + dev: true + + /@storybook/csf@0.0.1: + resolution: {integrity: sha512-USTLkZze5gkel8MYCujSRBVIrUQ3YPBrLOx7GNk/0wttvVtlzWXAq9eLbQ4p/NicGxP+3T7KPEMVV//g+yubpw==} + dependencies: + lodash: 4.17.21 + dev: true + + /@storybook/csf@0.1.12: + resolution: {integrity: sha512-9/exVhabisyIVL0VxTCxo01Tdm8wefIXKXfltAPTSr8cbLn5JAxGQ6QV3mjdecLGEOucfoVhAKtJfVHxEK1iqw==} + dependencies: + type-fest: 2.19.0 + dev: true + + /@storybook/csf@0.1.13: + resolution: {integrity: sha512-7xOOwCLGB3ebM87eemep89MYRFTko+D8qE7EdAAq74lgdqRR5cOUtYWJLjO2dLtP94nqoOdHJo6MdLLKzg412Q==} + dependencies: + type-fest: 2.19.0 + dev: true + + /@storybook/global@5.0.0: + resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} + dev: true + + /@storybook/icons@1.4.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Td73IeJxOyalzvjQL+JXx72jlIYHgs+REaHiREOqfpo3A2AYYG71AUbcv+lg7mEDIweKVCxsMQ0UKo634c8XeA==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: true + + /@storybook/instrumenter@8.6.11(storybook@8.6.11): + resolution: {integrity: sha512-IOLvc4BOVKofWhyRScA2h/R36cACBK3DUCrZkQTLU+FUIXWg7Yjyco7lDEk/0W9mlH0Fe/eWEhluI2WTowkesQ==} + peerDependencies: + storybook: ^8.6.11 + dependencies: + '@storybook/global': 5.0.0 + '@vitest/utils': 2.1.8 + storybook: 8.6.11(prettier@3.5.3) + dev: true + + /@storybook/manager-api@8.6.11(storybook@8.6.11): + resolution: {integrity: sha512-U3ijEFX7B7wNYzFctmTIXOiN0zLlt8/9EHbZQUUrQ1pf7bQzADJCy63Y3B+kir8i+n3LsBWB42X2aSiT0lLaKQ==} + peerDependencies: + storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 + dependencies: + storybook: 8.6.11(prettier@3.5.3) + dev: true + + /@storybook/preview-api@8.6.11(storybook@8.6.11): + resolution: {integrity: sha512-NpSVJFa9MkPq3u/h+bvx+iSnm6OG6mMUzMgmY67mA0dgIgOWcaoP2Y7254SZlBeho97HCValTDKJyqZMwiVlyQ==} + peerDependencies: + storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 + dependencies: + storybook: 8.6.11(prettier@3.5.3) + dev: true + + /@storybook/react-dom-shim@8.6.11(react-dom@18.2.0)(react@18.2.0)(storybook@8.6.11): + resolution: {integrity: sha512-giwqwx0PO70SyqoZ25CBM2tpAjJX5sjPm7uWKknYcFGl3H2PYDqqnvH7NfEXENQMq5DpAJisCZ0KkRvNHzLV2w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta + storybook: ^8.6.11 + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + storybook: 8.6.11(prettier@3.5.3) + dev: true + + /@storybook/svelte-vite@8.6.11(@babel/core@7.20.12)(@sveltejs/vite-plugin-svelte@5.0.3)(postcss-load-config@3.1.4)(postcss@8.4.31)(storybook@8.6.11)(svelte@5.25.5)(vite@6.2.7): + resolution: {integrity: sha512-umLoL/B7+P4Yb9ENe4kP8cpRWVg3WnfaVkMrnNzKG/Di7pQePeRY+KNP/RBVA/+8QpF3kwU00KNHKmpUYDzP/Q==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 + storybook: ^8.6.11 + svelte: ^4.0.0 || ^5.0.0 + vite: ^4.0.0 || ^5.0.0 || ^6.0.0 + dependencies: + '@storybook/builder-vite': 8.6.11(storybook@8.6.11)(vite@6.2.7) + '@storybook/svelte': 8.6.11(storybook@8.6.11)(svelte@5.25.5) + '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.25.5)(vite@6.2.7) + magic-string: 0.30.17 + storybook: 8.6.11(prettier@3.5.3) + svelte: 5.25.5 + svelte-preprocess: 5.1.4(@babel/core@7.20.12)(postcss-load-config@3.1.4)(postcss@8.4.31)(svelte@5.25.5)(typescript@5.2.2) + svelte2tsx: 0.7.35(svelte@5.25.5)(typescript@5.2.2) + sveltedoc-parser: 4.2.1 + ts-dedent: 2.2.0 + typescript: 5.2.2 + vite: 6.2.7(@types/node@18.15.3) + transitivePeerDependencies: + - '@babel/core' + - coffeescript + - less + - postcss + - postcss-load-config + - pug + - sass + - stylus + - sugarss + - supports-color + dev: true + + /@storybook/svelte@8.6.11(storybook@8.6.11)(svelte@5.25.5): + resolution: {integrity: sha512-MHVsBKkFmFRWRVgxn3fqqAEYrCD5mXI9Dmn9fkTvDqvzDGMG++X5DgdaJE61i8Ws4xeaM23c7uS+MDpbyOm8ag==} + engines: {node: '>=18.0.0'} + peerDependencies: + storybook: ^8.6.11 + svelte: ^4.0.0 || ^5.0.0 + dependencies: + '@storybook/components': 8.6.11(storybook@8.6.11) + '@storybook/csf': 0.1.12 + '@storybook/global': 5.0.0 + '@storybook/manager-api': 8.6.11(storybook@8.6.11) + '@storybook/preview-api': 8.6.11(storybook@8.6.11) + '@storybook/theming': 8.6.11(storybook@8.6.11) + storybook: 8.6.11(prettier@3.5.3) + svelte: 5.25.5 + sveltedoc-parser: 4.2.1 + ts-dedent: 2.2.0 + type-fest: 2.19.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@storybook/sveltekit@8.6.11(@babel/core@7.20.12)(@sveltejs/vite-plugin-svelte@5.0.3)(postcss-load-config@3.1.4)(postcss@8.4.31)(storybook@8.6.11)(svelte@5.25.5)(vite@6.2.7): + resolution: {integrity: sha512-PzC+dJZXm9150rrtgRC2LHDwrD/ZVR/8+QQwsEBZYjzwU8uX3VTFqQmymVWlvSF7xHyF3rq5iPvOoQTmX4BMSA==} + engines: {node: '>=18.0.0'} + peerDependencies: + storybook: ^8.6.11 + svelte: ^4.0.0 || ^5.0.0 + vite: ^4.0.0 || ^5.0.0 || ^6.0.0 + dependencies: + '@storybook/addon-actions': 8.6.11(storybook@8.6.11) + '@storybook/builder-vite': 8.6.11(storybook@8.6.11)(vite@6.2.7) + '@storybook/svelte': 8.6.11(storybook@8.6.11)(svelte@5.25.5) + '@storybook/svelte-vite': 8.6.11(@babel/core@7.20.12)(@sveltejs/vite-plugin-svelte@5.0.3)(postcss-load-config@3.1.4)(postcss@8.4.31)(storybook@8.6.11)(svelte@5.25.5)(vite@6.2.7) + storybook: 8.6.11(prettier@3.5.3) + svelte: 5.25.5 + vite: 6.2.7(@types/node@18.15.3) + transitivePeerDependencies: + - '@babel/core' + - '@sveltejs/vite-plugin-svelte' + - coffeescript + - less + - postcss + - postcss-load-config + - pug + - sass + - stylus + - sugarss + - supports-color + dev: true + + /@storybook/test-runner@0.22.0(@types/node@18.15.3)(storybook@8.6.11): + resolution: {integrity: sha512-fKY6MTE/bcvMaulKXy+z0fPmRXJx1REkYMOMcGn8zn6uffyBigGgaVf/sZ+AZfibwvjzg/StWhJ9HvAM8pc14g==} + engines: {node: ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + storybook: ^0.0.0-0 || ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 || ^9.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@babel/generator': 7.24.7 + '@babel/template': 7.24.7 + '@babel/types': 7.26.9 + '@jest/types': 29.6.3 + '@storybook/csf': 0.1.13 + '@swc/core': 1.10.7 + '@swc/jest': 0.2.36(@swc/core@1.10.7) + expect-playwright: 0.8.0 + jest: 29.7.0(@types/node@18.15.3) + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-junit: 16.0.0 + jest-playwright-preset: 4.0.0(jest-circus@29.7.0)(jest-environment-node@29.7.0)(jest-runner@29.7.0)(jest@29.7.0) + jest-runner: 29.7.0 + jest-serializer-html: 7.1.0 + jest-watch-typeahead: 2.2.2(jest@29.7.0) + nyc: 15.1.0 + playwright: 1.50.1 + storybook: 8.6.11(prettier@3.5.3) + transitivePeerDependencies: + - '@swc/helpers' + - '@types/node' + - babel-plugin-macros + - debug + - node-notifier + - supports-color + - ts-node + dev: true + + /@storybook/test@8.6.11(storybook@8.6.11): + resolution: {integrity: sha512-bk8JCRmVRHjnyt+/YnzCMEd4Y/K2L3uM+sCNuH4pYw6XT2UkR3Dj5mScGnfMvm98lHfpZDcD/AbY2vorOQsq+g==} + peerDependencies: + storybook: ^8.6.11 + dependencies: + '@storybook/global': 5.0.0 + '@storybook/instrumenter': 8.6.11(storybook@8.6.11) + '@testing-library/dom': 10.4.0 + '@testing-library/jest-dom': 6.5.0 + '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0) + '@vitest/expect': 2.0.5 + '@vitest/spy': 2.0.5 + storybook: 8.6.11(prettier@3.5.3) + dev: true + + /@storybook/theming@8.6.11(storybook@8.6.11): + resolution: {integrity: sha512-G7IK5P9gzofUjfYhMo9Pdgbqcr22eoKFLD808Q8RxJopDoypdZKg4tes2iD+6YnrtnHS0nEoP/soMmfFYl9FIw==} + peerDependencies: + storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 + dependencies: + storybook: 8.6.11(prettier@3.5.3) + dev: true + + /@sveltejs/acorn-typescript@1.0.5(acorn@8.14.0): + resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==} + peerDependencies: + acorn: ^8.9.0 + dependencies: + acorn: 8.14.0 + + /@sveltejs/adapter-static@3.0.8(@sveltejs/kit@2.20.2): + resolution: {integrity: sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==} + peerDependencies: + '@sveltejs/kit': ^2.0.0 + dependencies: + '@sveltejs/kit': 2.20.2(@sveltejs/vite-plugin-svelte@5.0.3)(svelte@5.25.5)(vite@6.2.7) + dev: true + + /@sveltejs/adapter-vercel@4.0.0(@sveltejs/kit@2.20.2): + resolution: {integrity: sha512-Qt8BY/Z7B0EXSeu+7mLhHoMdxeU7/AzIDRwH2TWgxT+eowiYxm9ZdtsLxVrXHHwborWlK2nbwfBsZr3FDXVxkQ==} + peerDependencies: + '@sveltejs/kit': ^2.0.0 + dependencies: + '@sveltejs/kit': 2.20.2(@sveltejs/vite-plugin-svelte@5.0.3)(svelte@5.25.5)(vite@6.2.7) + '@vercel/nft': 0.24.4 + esbuild: 0.19.12 + transitivePeerDependencies: + - encoding + - supports-color + dev: true + + /@sveltejs/kit@2.20.2(@sveltejs/vite-plugin-svelte@5.0.3)(svelte@5.25.5)(vite@6.2.7): + resolution: {integrity: sha512-Dv8TOAZC9vyfcAB9TMsvUEJsRbklRTeNfcYBPaeH6KnABJ99i3CvCB2eNx8fiiliIqe+9GIchBg4RodRH5p1BQ==} + engines: {node: '>=18.13'} + hasBin: true + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.3 || ^6.0.0 + dependencies: + '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.25.5)(vite@6.2.7) + '@types/cookie': 0.6.0 + cookie: 0.6.0 + devalue: 5.1.1 + esm-env: 1.2.2 + import-meta-resolve: 4.1.0 + kleur: 4.1.5 + magic-string: 0.30.17 + mrmime: 2.0.0 + sade: 1.8.1 + set-cookie-parser: 2.6.0 + sirv: 3.0.1 + svelte: 5.25.5 + vite: 6.2.7(@types/node@18.15.3) + + /@sveltejs/package@2.3.10(svelte@5.25.5)(typescript@5.2.2): + resolution: {integrity: sha512-A4fQacgjJ7C/7oSmxR61/TdB14u6ecyMZ8V9JCR5Lol0bLj/PdJPU4uFodFBsKzO3iFiJMpNTgZZ+zYsYZNpUg==} + engines: {node: ^16.14 || >=18} + hasBin: true + peerDependencies: + svelte: ^3.44.0 || ^4.0.0 || ^5.0.0-next.1 + dependencies: + chokidar: 4.0.3 + kleur: 4.1.5 + sade: 1.8.1 + semver: 7.6.3 + svelte: 5.25.5 + svelte2tsx: 0.7.35(svelte@5.25.5)(typescript@5.2.2) + transitivePeerDependencies: + - typescript + dev: false + + /@sveltejs/svelte-virtual-list@3.0.1: + resolution: {integrity: sha512-aF9TptS7NKKS7/TqpsxQBSDJ9Q0XBYzBehCeIC5DzdMEgrJZpIYao9LRLnyyo6SVodpapm2B7FE/Lj+FSA5/SQ==} + dev: false + + /@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.0.3)(svelte@5.25.5)(vite@6.2.7): + resolution: {integrity: sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^5.0.0 + svelte: ^5.0.0 + vite: ^6.0.0 + dependencies: + '@sveltejs/vite-plugin-svelte': 5.0.3(svelte@5.25.5)(vite@6.2.7) + debug: 4.4.0 + svelte: 5.25.5 + vite: 6.2.7(@types/node@18.15.3) + transitivePeerDependencies: + - supports-color + + /@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.25.5)(vite@6.2.7): + resolution: {integrity: sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + svelte: ^5.0.0 + vite: ^6.0.0 + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.0.3)(svelte@5.25.5)(vite@6.2.7) + debug: 4.4.0 + deepmerge: 4.3.1 + kleur: 4.1.5 + magic-string: 0.30.17 + svelte: 5.25.5 + vite: 6.2.7(@types/node@18.15.3) + vitefu: 1.0.6(vite@6.2.7) + transitivePeerDependencies: + - supports-color + + /@swc/core-darwin-arm64@1.10.7: + resolution: {integrity: sha512-SI0OFg987P6hcyT0Dbng3YRISPS9uhLX1dzW4qRrfqQdb0i75lPJ2YWe9CN47HBazrIA5COuTzrD2Dc0TcVsSQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@swc/core-darwin-x64@1.10.7: + resolution: {integrity: sha512-RFIAmWVicD/l3RzxgHW0R/G1ya/6nyMspE2cAeDcTbjHi0I5qgdhBWd6ieXOaqwEwiCd0Mot1g2VZrLGoBLsjQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-arm-gnueabihf@1.10.7: + resolution: {integrity: sha512-QP8vz7yELWfop5mM5foN6KkLylVO7ZUgWSF2cA0owwIaziactB2hCPZY5QU690coJouk9KmdFsPWDnaCFUP8tg==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-arm64-gnu@1.10.7: + resolution: {integrity: sha512-NgUDBGQcOeLNR+EOpmUvSDIP/F7i/OVOKxst4wOvT5FTxhnkWrW+StJGKj+DcUVSK5eWOYboSXr1y+Hlywwokw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-arm64-musl@1.10.7: + resolution: {integrity: sha512-gp5Un3EbeSThBIh6oac5ZArV/CsSmTKj5jNuuUAuEsML3VF9vqPO+25VuxCvsRf/z3py+xOWRaN2HY/rjMeZog==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-x64-gnu@1.10.7: + resolution: {integrity: sha512-k/OxLLMl/edYqbZyUNg6/bqEHTXJT15l9WGqsl/2QaIGwWGvles8YjruQYQ9d4h/thSXLT9gd8bExU2D0N+bUA==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-x64-musl@1.10.7: + resolution: {integrity: sha512-XeDoURdWt/ybYmXLCEE8aSiTOzEn0o3Dx5l9hgt0IZEmTts7HgHHVeRgzGXbR4yDo0MfRuX5nE1dYpTmCz0uyA==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-win32-arm64-msvc@1.10.7: + resolution: {integrity: sha512-nYAbi/uLS+CU0wFtBx8TquJw2uIMKBnl04LBmiVoFrsIhqSl+0MklaA9FVMGA35NcxSJfcm92Prl2W2LfSnTqQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core-win32-ia32-msvc@1.10.7: + resolution: {integrity: sha512-+aGAbsDsIxeLxw0IzyQLtvtAcI1ctlXVvVcXZMNXIXtTURM876yNrufRo4ngoXB3jnb1MLjIIjgXfFs/eZTUSw==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core-win32-x64-msvc@1.10.7: + resolution: {integrity: sha512-TBf4clpDBjF/UUnkKrT0/th76/zwvudk5wwobiTFqDywMApHip5O0VpBgZ+4raY2TM8k5+ujoy7bfHb22zu17Q==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core@1.10.7: + resolution: {integrity: sha512-py91kjI1jV5D5W/Q+PurBdGsdU5TFbrzamP7zSCqLdMcHkKi3rQEM5jkQcZr0MXXSJTaayLxS3MWYTBIkzPDrg==} + engines: {node: '>=10'} + requiresBuild: true + peerDependencies: + '@swc/helpers': '*' + peerDependenciesMeta: + '@swc/helpers': + optional: true + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.17 + optionalDependencies: + '@swc/core-darwin-arm64': 1.10.7 + '@swc/core-darwin-x64': 1.10.7 + '@swc/core-linux-arm-gnueabihf': 1.10.7 + '@swc/core-linux-arm64-gnu': 1.10.7 + '@swc/core-linux-arm64-musl': 1.10.7 + '@swc/core-linux-x64-gnu': 1.10.7 + '@swc/core-linux-x64-musl': 1.10.7 + '@swc/core-win32-arm64-msvc': 1.10.7 + '@swc/core-win32-ia32-msvc': 1.10.7 + '@swc/core-win32-x64-msvc': 1.10.7 + dev: true + + /@swc/counter@0.1.3: + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + dev: true + + /@swc/jest@0.2.36(@swc/core@1.10.7): + resolution: {integrity: sha512-8X80dp81ugxs4a11z1ka43FPhP+/e+mJNXJSxiNYk8gIX/jPBtY4gQTrKu/KIoco8bzKuPI5lUxjfLiGsfvnlw==} + engines: {npm: '>= 7.0.0'} + peerDependencies: + '@swc/core': '*' + dependencies: + '@jest/create-cache-key-function': 29.7.0 + '@swc/core': 1.10.7 + '@swc/counter': 0.1.3 + jsonc-parser: 3.2.0 + dev: true + + /@swc/types@0.1.17: + resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==} + dependencies: + '@swc/counter': 0.1.3 + dev: true + + /@temporalio/activity@1.11.8: + resolution: {integrity: sha512-XWjj/EL0YiKsnh0W4W5kBm+Xp1jhpUEaQMGA25+BxKEYBn8qJ+mLQOwuqWn6punS9a6bvnT7P/pUlnhjPxH6KQ==} + dependencies: + '@temporalio/common': 1.11.8 + abort-controller: 3.0.0 + dev: true + + /@temporalio/client@1.11.8: + resolution: {integrity: sha512-UpiU+awykWYjQSSJH5NLY5Id/uDdiD9yz5oaardv1YF80rMN7U1Q6W9lgPEbg/b62qOv3Mia1+fE5hrav7ying==} + dependencies: + '@grpc/grpc-js': 1.10.9 + '@temporalio/common': 1.11.8 + '@temporalio/proto': 1.11.8 + abort-controller: 3.0.0 + long: 5.2.3 + uuid: 9.0.1 + dev: true + + /@temporalio/common@1.11.8: + resolution: {integrity: sha512-hB+TSB/P2Vm8chYyZGcKALdywGvcBSqzSUzFG1lOm/JPDh2d4uK6lvE2S8OneEndlJSdzE++d2Mt5B+iKe2ytA==} + dependencies: + '@temporalio/proto': 1.11.8 + long: 5.2.3 + ms: 3.0.0-canary.1 + proto3-json-serializer: 2.0.1 + dev: true + + /@temporalio/core-bridge@1.11.8: + resolution: {integrity: sha512-s1EkihwUHGIOylF31UFnsnntZmVqcKEgVTu8UvAf/J/br4jhs+nEO/mBFexqrzSWnraJhpq9UnzTNAklNHFVKg==} + requiresBuild: true + dependencies: + '@temporalio/common': 1.11.8 + arg: 5.0.2 + cargo-cp-artifact: 0.1.8 + which: 4.0.0 + dev: true + + /@temporalio/proto@1.11.8: + resolution: {integrity: sha512-L6QsIAq3PrnQpARAcqLCe4xMpbkpBEvA/9FQGddpzCESvViUOUAIHPwckL1DokYPedEAt19C+uuBGVBMCNx7vw==} + dependencies: + long: 5.2.3 + protobufjs: 7.2.5 + dev: true + + /@temporalio/testing@1.11.8(esbuild@0.25.0): + resolution: {integrity: sha512-FC6A2YFDH6CDp7eeZ9aW9RDSzGxQqH6Old5UxBvIk5ZFgsCMOltpWqYMTIrv9EQbe6xMOTgPjP/6XgAqvIPj/g==} + dependencies: + '@temporalio/activity': 1.11.8 + '@temporalio/client': 1.11.8 + '@temporalio/common': 1.11.8 + '@temporalio/core-bridge': 1.11.8 + '@temporalio/proto': 1.11.8 + '@temporalio/worker': 1.11.8(esbuild@0.25.0) + '@temporalio/workflow': 1.11.8 + abort-controller: 3.0.0 + transitivePeerDependencies: + - '@swc/helpers' + - esbuild + - uglify-js + - webpack-cli + dev: true + + /@temporalio/worker@1.11.8(esbuild@0.25.0): + resolution: {integrity: sha512-KNUEw8khhFoVLhOy93j9EkA6KfKtOVn7N7+c4kn9AM7QXkcxe73+1AUwNKgFYKZph4i6I26NBxs7MPrgh3GKxQ==} + engines: {node: '>= 16.0.0'} + dependencies: + '@swc/core': 1.10.7 + '@temporalio/activity': 1.11.8 + '@temporalio/client': 1.11.8 + '@temporalio/common': 1.11.8 + '@temporalio/core-bridge': 1.11.8 + '@temporalio/proto': 1.11.8 + '@temporalio/workflow': 1.11.8 + abort-controller: 3.0.0 + heap-js: 2.3.0 + memfs: 4.7.7 + rxjs: 7.8.1 + source-map: 0.7.4 + source-map-loader: 4.0.2(webpack@5.94.0) + supports-color: 8.1.1 + swc-loader: 0.2.3(@swc/core@1.10.7)(webpack@5.94.0) + unionfs: 4.5.1 + webpack: 5.94.0(@swc/core@1.10.7)(esbuild@0.25.0) + transitivePeerDependencies: + - '@swc/helpers' + - esbuild + - uglify-js + - webpack-cli + dev: true + + /@temporalio/workflow@1.11.8: + resolution: {integrity: sha512-gJy5Rr4WQCejdWskluHY/C20om0Yvx7/cLl9KHCweTyDLRfTu2VtMJRS8LPFQkKzoHBbLaqzPo2XdHh/u4JQoA==} + dependencies: + '@temporalio/common': 1.11.8 + '@temporalio/proto': 1.11.8 + dev: true + + /@testing-library/dom@10.4.0: + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} + engines: {node: '>=18'} + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/runtime': 7.23.2 + '@types/aria-query': 5.0.3 + aria-query: 5.3.0 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + dev: true + + /@testing-library/jest-dom@6.5.0: + resolution: {integrity: sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + dependencies: + '@adobe/css-tools': 4.4.1 + aria-query: 5.3.2 + chalk: 3.0.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + lodash: 4.17.21 + redent: 3.0.0 + dev: true + + /@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0): + resolution: {integrity: sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + dependencies: + '@testing-library/dom': 10.4.0 + dev: true + + /@tootallnate/once@2.0.0: + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + dev: true + + /@trysound/sax@0.2.0: + resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} + engines: {node: '>=10.13.0'} + dev: true + + /@types/aria-query@5.0.3: + resolution: {integrity: sha512-0Z6Tr7wjKJIk4OUEjVUQMtyunLDy339vcMaj38Kpj6jM2OE1p3S4kXExKZ7a3uXQAPCoy3sbrP1wibDKaf39oA==} + dev: true + + /@types/babel__core@7.20.5: + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + dependencies: + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 + dev: true + + /@types/babel__generator@7.6.8: + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + dependencies: + '@babel/types': 7.26.9 + dev: true + + /@types/babel__template@7.4.4: + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + dependencies: + '@babel/parser': 7.26.9 + '@babel/types': 7.26.9 + dev: true + + /@types/babel__traverse@7.20.6: + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + dependencies: + '@babel/types': 7.26.9 + dev: true + + /@types/base-64@1.0.2: + resolution: {integrity: sha512-uPgKMmM9fmn7I+Zi6YBqctOye4SlJsHKcisjHIMWpb2YKZRc36GpKyNuQ03JcT+oNXg1m7Uv4wU94EVltn8/cw==} + dev: true + + /@types/body-parser@1.19.2: + resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} + dependencies: + '@types/connect': 3.4.35 + '@types/node': 18.15.3 + dev: true + + /@types/connect@3.4.35: + resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} + dependencies: + '@types/node': 18.15.3 + dev: true + + /@types/cookie@0.6.0: + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + + /@types/cors@2.8.13: + resolution: {integrity: sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==} + dependencies: + '@types/node': 18.15.3 + dev: true + + /@types/debug@4.1.8: + resolution: {integrity: sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==} + dependencies: + '@types/ms': 0.7.31 + + /@types/estree@1.0.6: + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + /@types/estree@1.0.7: + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + + /@types/express-serve-static-core@4.17.35: + resolution: {integrity: sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==} + dependencies: + '@types/node': 18.15.3 + '@types/qs': 6.9.7 + '@types/range-parser': 1.2.4 + '@types/send': 0.17.1 + dev: true + + /@types/express@4.17.17: + resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==} + dependencies: + '@types/body-parser': 1.19.2 + '@types/express-serve-static-core': 4.17.35 + '@types/qs': 6.9.7 + '@types/serve-static': 1.15.1 + dev: true + + /@types/extend@3.0.1: + resolution: {integrity: sha512-R1g/VyKFFI2HLC1QGAeTtCBWCo6n75l41OnsVYNbmKG+kempOESaodf6BeJyUM3Q0rKa/NQcTHbB2+66lNnxLw==} + dev: true + + /@types/fs-extra@9.0.13: + resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} + dependencies: + '@types/node': 18.15.3 + dev: true + + /@types/graceful-fs@4.1.9: + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + dependencies: + '@types/node': 18.15.3 + dev: true + + /@types/hast@2.3.5: + resolution: {integrity: sha512-SvQi0L/lNpThgPoleH53cdjB3y9zpLlVjRbqB3rH8hx1jiRSBGAhyjV3H+URFjNVRqt2EdYNrbZE5IsGlNfpRg==} + dependencies: + '@types/unist': 2.0.8 + dev: true + + /@types/hast@3.0.4: + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + dependencies: + '@types/unist': 3.0.0 + dev: false + + /@types/istanbul-lib-coverage@2.0.4: + resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} + dev: true + + /@types/istanbul-lib-report@3.0.3: + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + dependencies: + '@types/istanbul-lib-coverage': 2.0.4 + dev: true + + /@types/istanbul-reports@3.0.4: + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + dependencies: + '@types/istanbul-lib-report': 3.0.3 + dev: true + + /@types/json-bigint@1.0.1: + resolution: {integrity: sha512-zpchZLNsNuzJHi6v64UBoFWAvQlPhch7XAi36FkH6tL1bbbmimIF+cS7vwkzY4u5RaSWMoflQfu+TshMPPw8uw==} + dev: true + + /@types/json-schema@7.0.12: + resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} + dev: true + + /@types/json-schema@7.0.15: + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + requiresBuild: true + dev: false + optional: true + + /@types/json5@0.0.29: + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + dev: true + + /@types/junit-report-builder@3.0.2: + resolution: {integrity: sha512-R5M+SYhMbwBeQcNXYWNCZkl09vkVfAtcPIaCGdzIkkbeaTrVbGQ7HVgi4s+EmM/M1K4ZuWQH0jGcvMvNePfxYA==} + dev: true + + /@types/mdast@3.0.12: + resolution: {integrity: sha512-DT+iNIRNX884cx0/Q1ja7NyUPpZuv0KPyL5rGNxm1WC1OtHstl7n4Jb7nk+xacNShQMbczJjt8uFzznpp6kYBg==} + dependencies: + '@types/unist': 2.0.8 + + /@types/mdast@4.0.4: + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + dependencies: + '@types/unist': 3.0.0 + dev: false + + /@types/mdx@2.0.9: + resolution: {integrity: sha512-OKMdj17y8Cs+k1r0XFyp59ChSOwf8ODGtMQ4mnpfz5eFDk1aO41yN3pSKGuvVzmWAkFp37seubY1tzOVpwfWwg==} + dev: true + + /@types/mime@1.3.2: + resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} + dev: true + + /@types/mime@3.0.1: + resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==} + dev: true + + /@types/minimist@1.2.2: + resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} + dev: true + + /@types/mkdirp@1.0.2: + resolution: {integrity: sha512-o0K1tSO0Dx5X6xlU5F1D6625FawhC3dU3iqr25lluNv/+/QIVH8RLNEiVokgIZo+mz+87w/3Mkg/VvQS+J51fQ==} + dependencies: + '@types/node': 18.15.3 + dev: true + + /@types/ms@0.7.31: + resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} + + /@types/node@18.15.3: + resolution: {integrity: sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==} + + /@types/normalize-package-data@2.4.1: + resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} + dev: true + + /@types/parse5@6.0.3: + resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} + dev: true + + /@types/ps-tree@1.1.2: + resolution: {integrity: sha512-ZREFYlpUmPQJ0esjxoG1fMvB2HNaD3z+mjqdSosZvd3RalncI9NEur73P8ZJz4YQdL64CmV1w0RuqoRUlhQRBw==} + dev: true + + /@types/pug@2.0.6: + resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==} + dev: true + + /@types/qs@6.9.7: + resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} + dev: true + + /@types/range-parser@1.2.4: + resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} + dev: true + + /@types/react@19.1.2: + resolution: {integrity: sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==} + dependencies: + csstype: 3.1.3 + dev: true + + /@types/sanitize-html@2.8.0: + resolution: {integrity: sha512-Uih6caOm3DsBYnVGOYn0A9NoTNe1c4aPStmHC/YA2JrpP9kx//jzaRcIklFvSpvVQEcpl/ZCr4DgISSf/YxTvg==} + dependencies: + htmlparser2: 8.0.1 + dev: true + + /@types/semver@7.5.0: + resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} + dev: true + + /@types/send@0.17.1: + resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==} + dependencies: + '@types/mime': 1.3.2 + '@types/node': 18.15.3 + dev: true + + /@types/serve-static@1.15.1: + resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==} + dependencies: + '@types/mime': 3.0.1 + '@types/node': 18.15.3 + dev: true + + /@types/stack-utils@2.0.3: + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + dev: true + + /@types/tar-fs@2.0.4: + resolution: {integrity: sha512-ipPec0CjTmVDWE+QKr9cTmIIoTl7dFG/yARCM5MqK8i6CNLIG1P8x4kwDsOQY1ChZOZjH0wO9nvfgBvWl4R3kA==} + dependencies: + '@types/node': 18.15.3 + '@types/tar-stream': 2.2.2 + dev: true + + /@types/tar-stream@2.2.2: + resolution: {integrity: sha512-1AX+Yt3icFuU6kxwmPakaiGrJUwG44MpuiqPg4dSolRFk6jmvs4b3IbUol9wKDLIgU76gevn3EwE8y/DkSJCZQ==} + dependencies: + '@types/node': 18.15.3 + dev: true + + /@types/unist@2.0.8: + resolution: {integrity: sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==} + + /@types/unist@3.0.0: + resolution: {integrity: sha512-MFETx3tbTjE7Uk6vvnWINA/1iJ7LuMdO4fcq8UfF0pRbj01aGLduVvQcRyswuACJdpnHgg8E3rQLhaRdNEJS0w==} + + /@types/uuid@9.0.0: + resolution: {integrity: sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==} + dev: true + + /@types/uuid@9.0.8: + resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==} + dev: true + + /@types/validator@13.15.2: + resolution: {integrity: sha512-y7pa/oEJJ4iGYBxOpfAKn5b9+xuihvzDVnC/OSvlVnGxVg0pOqmjiMafiJ1KVNQEaPZf9HsEp5icEwGg8uIe5Q==} + requiresBuild: true + dev: false + optional: true + + /@types/wait-on@5.3.4: + resolution: {integrity: sha512-EBsPjFMrFlMbbUFf9D1Fp+PAB2TwmUn7a3YtHyD9RLuTIk1jDd8SxXVAoez2Ciy+8Jsceo2MYEYZzJ/DvorOKw==} + dependencies: + '@types/node': 18.15.3 + dev: true + + /@types/which@2.0.1: + resolution: {integrity: sha512-Jjakcv8Roqtio6w1gr0D7y6twbhx6gGgFGF5BLwajPpnOIOxFkakFhCq+LmyyeAz7BX6ULrjBOxdKaCDy+4+dQ==} + dev: true + + /@types/yargs-parser@21.0.0: + resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} + dev: true + + /@types/yargs@17.0.24: + resolution: {integrity: sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==} + dependencies: + '@types/yargs-parser': 21.0.0 + dev: true + + /@typeschema/class-validator@0.3.0(class-validator@0.14.2): + resolution: {integrity: sha512-OJSFeZDIQ8EK1HTljKLT5CItM2wsbgczLN8tMEfz3I1Lmhc5TBfkZ0eikFzUC16tI3d1Nag7um6TfCgp2I2Bww==} + requiresBuild: true + peerDependencies: + class-validator: ^0.14.1 + peerDependenciesMeta: + class-validator: + optional: true + dependencies: + '@typeschema/core': 0.14.0 + class-validator: 0.14.2 + transitivePeerDependencies: + - '@types/json-schema' + dev: false + optional: true + + /@typeschema/core@0.14.0: + resolution: {integrity: sha512-Ia6PtZHcL3KqsAWXjMi5xIyZ7XMH4aSnOQes8mfMLx+wGFGtGRNlwe6Y7cYvX+WfNK67OL0/HSe9t8QDygV0/w==} + requiresBuild: true + peerDependencies: + '@types/json-schema': ^7.0.15 + peerDependenciesMeta: + '@types/json-schema': + optional: true + dev: false + optional: true + + /@typescript-eslint/eslint-plugin@6.6.0(@typescript-eslint/parser@6.6.0)(eslint@8.47.0)(typescript@5.2.2): + resolution: {integrity: sha512-CW9YDGTQnNYMIo5lMeuiIG08p4E0cXrXTbcZ2saT/ETE7dWUrNxlijsQeU04qAAKkILiLzdQz+cGFxCJjaZUmA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.7.0 + '@typescript-eslint/parser': 6.6.0(eslint@8.47.0)(typescript@5.2.2) + '@typescript-eslint/scope-manager': 6.6.0 + '@typescript-eslint/type-utils': 6.6.0(eslint@8.47.0)(typescript@5.2.2) + '@typescript-eslint/utils': 6.6.0(eslint@8.47.0)(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.6.0 + debug: 4.3.4 + eslint: 8.47.0 + graphemer: 1.4.0 + ignore: 5.2.4 + natural-compare: 1.4.0 + semver: 7.5.4 + ts-api-utils: 1.0.2(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/parser@6.6.0(eslint@8.47.0)(typescript@5.2.2): + resolution: {integrity: sha512-setq5aJgUwtzGrhW177/i+DMLqBaJbdwGj2CPIVFFLE0NCliy5ujIdLHd2D1ysmlmsjdL2GWW+hR85neEfc12w==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 6.6.0 + '@typescript-eslint/types': 6.6.0 + '@typescript-eslint/typescript-estree': 6.6.0(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.6.0 + debug: 4.3.4 + eslint: 8.47.0 + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/scope-manager@5.62.0: + resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + dev: true + + /@typescript-eslint/scope-manager@6.4.1: + resolution: {integrity: sha512-p/OavqOQfm4/Hdrr7kvacOSFjwQ2rrDVJRPxt/o0TOWdFnjJptnjnZ+sYDR7fi4OimvIuKp+2LCkc+rt9fIW+A==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.4.1 + '@typescript-eslint/visitor-keys': 6.4.1 + dev: true + + /@typescript-eslint/scope-manager@6.6.0: + resolution: {integrity: sha512-pT08u5W/GT4KjPUmEtc2kSYvrH8x89cVzkA0Sy2aaOUIw6YxOIjA8ilwLr/1fLjOedX1QAuBpG9XggWqIIfERw==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.6.0 + '@typescript-eslint/visitor-keys': 6.6.0 + dev: true + + /@typescript-eslint/type-utils@6.6.0(eslint@8.47.0)(typescript@5.2.2): + resolution: {integrity: sha512-8m16fwAcEnQc69IpeDyokNO+D5spo0w1jepWWY2Q6y5ZKNuj5EhVQXjtVAeDDqvW6Yg7dhclbsz6rTtOvcwpHg==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 6.6.0(typescript@5.2.2) + '@typescript-eslint/utils': 6.6.0(eslint@8.47.0)(typescript@5.2.2) + debug: 4.3.4 + eslint: 8.47.0 + ts-api-utils: 1.0.2(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/types@5.62.0: + resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@typescript-eslint/types@6.4.1: + resolution: {integrity: sha512-zAAopbNuYu++ijY1GV2ylCsQsi3B8QvfPHVqhGdDcbx/NK5lkqMnCGU53amAjccSpk+LfeONxwzUhDzArSfZJg==} + engines: {node: ^16.0.0 || >=18.0.0} + dev: true + + /@typescript-eslint/types@6.6.0: + resolution: {integrity: sha512-CB6QpJQ6BAHlJXdwUmiaXDBmTqIE2bzGTDLADgvqtHWuhfNP3rAOK7kAgRMAET5rDRr9Utt+qAzRBdu3AhR3sg==} + engines: {node: ^16.0.0 || >=18.0.0} + dev: true + + /@typescript-eslint/typescript-estree@5.62.0(typescript@5.2.2): + resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + debug: 4.4.0 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.6.3 + tsutils: 3.21.0(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/typescript-estree@6.4.1(typescript@5.2.2): + resolution: {integrity: sha512-xF6Y7SatVE/OyV93h1xGgfOkHr2iXuo8ip0gbfzaKeGGuKiAnzS+HtVhSPx8Www243bwlW8IF7X0/B62SzFftg==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 6.4.1 + '@typescript-eslint/visitor-keys': 6.4.1 + debug: 4.4.0 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.6.3 + ts-api-utils: 1.0.2(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/typescript-estree@6.6.0(typescript@5.2.2): + resolution: {integrity: sha512-hMcTQ6Al8MP2E6JKBAaSxSVw5bDhdmbCEhGW/V8QXkb9oNsFkA4SBuOMYVPxD3jbtQ4R/vSODBsr76R6fP3tbA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 6.6.0 + '@typescript-eslint/visitor-keys': 6.6.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.5.4 + ts-api-utils: 1.0.2(typescript@5.2.2) + typescript: 5.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/utils@5.62.0(eslint@8.47.0)(typescript@5.2.2): + resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0) + '@types/json-schema': 7.0.12 + '@types/semver': 7.5.0 + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.2.2) + eslint: 8.47.0 + eslint-scope: 5.1.1 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/utils@6.4.1(eslint@8.47.0)(typescript@5.2.2): + resolution: {integrity: sha512-F/6r2RieNeorU0zhqZNv89s9bDZSovv3bZQpUNOmmQK1L80/cV4KEu95YUJWi75u5PhboFoKUJBnZ4FQcoqhDw==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0) + '@types/json-schema': 7.0.12 + '@types/semver': 7.5.0 + '@typescript-eslint/scope-manager': 6.4.1 + '@typescript-eslint/types': 6.4.1 + '@typescript-eslint/typescript-estree': 6.4.1(typescript@5.2.2) + eslint: 8.47.0 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/utils@6.6.0(eslint@8.47.0)(typescript@5.2.2): + resolution: {integrity: sha512-mPHFoNa2bPIWWglWYdR0QfY9GN0CfvvXX1Sv6DlSTive3jlMTUy+an67//Gysc+0Me9pjitrq0LJp0nGtLgftw==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0) + '@types/json-schema': 7.0.12 + '@types/semver': 7.5.0 + '@typescript-eslint/scope-manager': 6.6.0 + '@typescript-eslint/types': 6.6.0 + '@typescript-eslint/typescript-estree': 6.6.0(typescript@5.2.2) + eslint: 8.47.0 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/visitor-keys@5.62.0: + resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + '@typescript-eslint/types': 5.62.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /@typescript-eslint/visitor-keys@6.4.1: + resolution: {integrity: sha512-y/TyRJsbZPkJIZQXrHfdnxVnxyKegnpEvnRGNam7s3TRR2ykGefEWOhaef00/UUN3IZxizS7BTO3svd3lCOJRQ==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.4.1 + eslint-visitor-keys: 3.4.3 + dev: true + + /@typescript-eslint/visitor-keys@6.6.0: + resolution: {integrity: sha512-L61uJT26cMOfFQ+lMZKoJNbAEckLe539VhTxiGHrWl5XSKQgA0RTBZJW2HFPy5T0ZvPVSD93QsrTKDkfNwJGyQ==} + engines: {node: ^16.0.0 || >=18.0.0} + dependencies: + '@typescript-eslint/types': 6.6.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /@ungap/structured-clone@1.2.0: + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + dev: false + + /@vercel/nft@0.24.4: + resolution: {integrity: sha512-KjYAZty7boH5fi5udp6p+lNu6nawgs++pHW+3koErMgbRkkHuToGX/FwjN5clV1FcaM3udfd4zW/sUapkMgpZw==} + engines: {node: '>=16'} + hasBin: true + dependencies: + '@mapbox/node-pre-gyp': 1.0.10 + '@rollup/pluginutils': 4.2.1 + acorn: 8.14.0 + async-sema: 3.1.1 + bindings: 1.5.0 + estree-walker: 2.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + micromatch: 4.0.5 + node-gyp-build: 4.6.0 + resolve-from: 5.0.0 + transitivePeerDependencies: + - encoding + - supports-color + dev: true + + /@vinejs/compiler@3.0.0: + resolution: {integrity: sha512-v9Lsv59nR56+bmy2p0+czjZxsLHwaibJ+SV5iK9JJfehlJMa501jUJQqqz4X/OqKXrxtE3uTQmSqjUqzF3B2mw==} + engines: {node: '>=18.0.0'} + requiresBuild: true + dev: false + optional: true + + /@vinejs/vine@3.0.1: + resolution: {integrity: sha512-ZtvYkYpZOYdvbws3uaOAvTFuvFXoQGAtmzeiXu+XSMGxi5GVsODpoI9Xu9TplEMuD/5fmAtBbKb9cQHkWkLXDQ==} + engines: {node: '>=18.16.0'} + requiresBuild: true + dependencies: + '@poppinss/macroable': 1.0.4 + '@types/validator': 13.15.2 + '@vinejs/compiler': 3.0.0 + camelcase: 8.0.0 + dayjs: 1.11.13 + dlv: 1.1.3 + normalize-url: 8.0.2 + validator: 13.15.15 + dev: false + optional: true + + /@vitest/expect@2.0.5: + resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==} + dependencies: + '@vitest/spy': 2.0.5 + '@vitest/utils': 2.0.5 + chai: 5.2.0 + tinyrainbow: 1.2.0 + dev: true + + /@vitest/expect@3.1.1: + resolution: {integrity: sha512-q/zjrW9lgynctNbwvFtQkGK9+vvHA5UzVi2V8APrp1C6fG6/MuYYkmlx4FubuqLycCeSdHD5aadWfua/Vr0EUA==} + dependencies: + '@vitest/spy': 3.1.1 + '@vitest/utils': 3.1.1 + chai: 5.2.0 + tinyrainbow: 2.0.0 + dev: true + + /@vitest/mocker@3.1.1(vite@6.2.7): + resolution: {integrity: sha512-bmpJJm7Y7i9BBELlLuuM1J1Q6EQ6K5Ye4wcyOpOMXMcePYKSIYlpcrCm4l/O6ja4VJA5G2aMJiuZkZdnxlC3SA==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + dependencies: + '@vitest/spy': 3.1.1 + estree-walker: 3.0.3 + magic-string: 0.30.17 + vite: 6.2.7(@types/node@18.15.3) + dev: true + + /@vitest/pretty-format@2.0.5: + resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==} + dependencies: + tinyrainbow: 1.2.0 + dev: true + + /@vitest/pretty-format@2.1.8: + resolution: {integrity: sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==} + dependencies: + tinyrainbow: 1.2.0 + dev: true + + /@vitest/pretty-format@3.1.1: + resolution: {integrity: sha512-dg0CIzNx+hMMYfNmSqJlLSXEmnNhMswcn3sXO7Tpldr0LiGmg3eXdLLhwkv2ZqgHb/d5xg5F7ezNFRA1fA13yA==} + dependencies: + tinyrainbow: 2.0.0 + dev: true + + /@vitest/runner@3.1.1: + resolution: {integrity: sha512-X/d46qzJuEDO8ueyjtKfxffiXraPRfmYasoC4i5+mlLEJ10UvPb0XH5M9C3gWuxd7BAQhpK42cJgJtq53YnWVA==} + dependencies: + '@vitest/utils': 3.1.1 + pathe: 2.0.3 + dev: true + + /@vitest/snapshot@3.1.1: + resolution: {integrity: sha512-bByMwaVWe/+1WDf9exFxWWgAixelSdiwo2p33tpqIlM14vW7PRV5ppayVXtfycqze4Qhtwag5sVhX400MLBOOw==} + dependencies: + '@vitest/pretty-format': 3.1.1 + magic-string: 0.30.17 + pathe: 2.0.3 + dev: true + + /@vitest/spy@2.0.5: + resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==} + dependencies: + tinyspy: 3.0.2 + dev: true + + /@vitest/spy@3.1.1: + resolution: {integrity: sha512-+EmrUOOXbKzLkTDwlsc/xrwOlPDXyVk3Z6P6K4oiCndxz7YLpp/0R0UsWVOKT0IXWjjBJuSMk6D27qipaupcvQ==} + dependencies: + tinyspy: 3.0.2 + dev: true + + /@vitest/ui@3.1.1(vitest@3.1.1): + resolution: {integrity: sha512-2HpiRIYg3dlvAJBV9RtsVswFgUSJK4Sv7QhpxoP0eBGkYwzGIKP34PjaV00AULQi9Ovl6LGyZfsetxDWY5BQdQ==} + peerDependencies: + vitest: 3.1.1 + dependencies: + '@vitest/utils': 3.1.1 + fflate: 0.8.2 + flatted: 3.3.3 + pathe: 2.0.3 + sirv: 3.0.1 + tinyglobby: 0.2.12 + tinyrainbow: 2.0.0 + vitest: 3.1.1(@types/node@18.15.3)(@vitest/ui@3.1.1)(jsdom@20.0.3) + dev: true + + /@vitest/utils@2.0.5: + resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==} + dependencies: + '@vitest/pretty-format': 2.0.5 + estree-walker: 3.0.3 + loupe: 3.1.3 + tinyrainbow: 1.2.0 + dev: true + + /@vitest/utils@2.1.8: + resolution: {integrity: sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==} + dependencies: + '@vitest/pretty-format': 2.1.8 + loupe: 3.1.3 + tinyrainbow: 1.2.0 + dev: true + + /@vitest/utils@3.1.1: + resolution: {integrity: sha512-1XIjflyaU2k3HMArJ50bwSh3wKWPD6Q47wz/NUSmRV0zNywPc4w79ARjg/i/aNINHwA+mIALhUVqD9/aUvZNgg==} + dependencies: + '@vitest/pretty-format': 3.1.1 + loupe: 3.1.3 + tinyrainbow: 2.0.0 + dev: true + + /@webassemblyjs/ast@1.12.1: + resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} + dependencies: + '@webassemblyjs/helper-numbers': 1.11.6 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + dev: true + + /@webassemblyjs/floating-point-hex-parser@1.11.6: + resolution: {integrity: sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==} + dev: true + + /@webassemblyjs/helper-api-error@1.11.6: + resolution: {integrity: sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==} + dev: true + + /@webassemblyjs/helper-buffer@1.12.1: + resolution: {integrity: sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==} + dev: true + + /@webassemblyjs/helper-numbers@1.11.6: + resolution: {integrity: sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==} + dependencies: + '@webassemblyjs/floating-point-hex-parser': 1.11.6 + '@webassemblyjs/helper-api-error': 1.11.6 + '@xtuc/long': 4.2.2 + dev: true + + /@webassemblyjs/helper-wasm-bytecode@1.11.6: + resolution: {integrity: sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==} + dev: true + + /@webassemblyjs/helper-wasm-section@1.12.1: + resolution: {integrity: sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==} + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-buffer': 1.12.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/wasm-gen': 1.12.1 + dev: true + + /@webassemblyjs/ieee754@1.11.6: + resolution: {integrity: sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==} + dependencies: + '@xtuc/ieee754': 1.2.0 + dev: true + + /@webassemblyjs/leb128@1.11.6: + resolution: {integrity: sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==} + dependencies: + '@xtuc/long': 4.2.2 + dev: true + + /@webassemblyjs/utf8@1.11.6: + resolution: {integrity: sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==} + dev: true + + /@webassemblyjs/wasm-edit@1.12.1: + resolution: {integrity: sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==} + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-buffer': 1.12.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/helper-wasm-section': 1.12.1 + '@webassemblyjs/wasm-gen': 1.12.1 + '@webassemblyjs/wasm-opt': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + '@webassemblyjs/wast-printer': 1.12.1 + dev: true + + /@webassemblyjs/wasm-gen@1.12.1: + resolution: {integrity: sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==} + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/ieee754': 1.11.6 + '@webassemblyjs/leb128': 1.11.6 + '@webassemblyjs/utf8': 1.11.6 + dev: true + + /@webassemblyjs/wasm-opt@1.12.1: + resolution: {integrity: sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==} + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-buffer': 1.12.1 + '@webassemblyjs/wasm-gen': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + dev: true + + /@webassemblyjs/wasm-parser@1.12.1: + resolution: {integrity: sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==} + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/helper-api-error': 1.11.6 + '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/ieee754': 1.11.6 + '@webassemblyjs/leb128': 1.11.6 + '@webassemblyjs/utf8': 1.11.6 + dev: true + + /@webassemblyjs/wast-printer@1.12.1: + resolution: {integrity: sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==} + dependencies: + '@webassemblyjs/ast': 1.12.1 + '@xtuc/long': 4.2.2 + dev: true + + /@xtuc/ieee754@1.2.0: + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + dev: true + + /@xtuc/long@4.2.2: + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + dev: true + + /abab@2.0.6: + resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} + dev: true + + /abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + dev: true + + /abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + dependencies: + event-target-shim: 5.0.1 + dev: true + + /accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + dev: true + + /acorn-globals@7.0.1: + resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} + dependencies: + acorn: 8.8.1 + acorn-walk: 8.3.4 + dev: true + + /acorn-import-attributes@1.9.5(acorn@8.14.0): + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + dependencies: + acorn: 8.14.0 + dev: true + + /acorn-jsx@5.3.2(acorn@8.14.0): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.14.0 + dev: true + + /acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + dependencies: + acorn: 8.14.0 + dev: true + + /acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + + /acorn@8.8.1: + resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + dependencies: + debug: 4.4.0 + transitivePeerDependencies: + - supports-color + dev: true + + /aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + dev: true + + /ajv-keywords@3.5.2(ajv@6.12.6): + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 + dependencies: + ajv: 6.12.6 + dev: true + + /ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: true + + /ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + dev: true + + /ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + dev: true + + /ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.21.3 + dev: true + + /ansi-escapes@6.2.1: + resolution: {integrity: sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==} + engines: {node: '>=14.16'} + dev: true + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + dev: true + + /ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + dev: true + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true + + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + dev: true + + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: true + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /append-transform@2.0.0: + resolution: {integrity: sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==} + engines: {node: '>=8'} + dependencies: + default-require-extensions: 3.0.1 + dev: true + + /aproba@2.0.0: + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + dev: true + + /archy@1.0.0: + resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} + dev: true + + /are-we-there-yet@2.0.0: + resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} + engines: {node: '>=10'} + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.0 + dev: true + + /arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + dev: true + + /argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + dependencies: + sprintf-js: 1.0.3 + dev: true + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + dependencies: + dequal: 2.0.3 + dev: true + + /aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + /arktype@2.1.20: + resolution: {integrity: sha512-IZCEEXaJ8g+Ijd59WtSYwtjnqXiwM8sWQ5EjGamcto7+HVN9eK0C4p0zDlCuAwWhpqr6fIBkxPuYDl4/Mcj/+Q==} + requiresBuild: true + dependencies: + '@ark/schema': 0.46.0 + '@ark/util': 0.46.0 + dev: false + optional: true + + /array-buffer-byte-length@1.0.0: + resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + dependencies: + call-bind: 1.0.7 + is-array-buffer: 3.0.2 + dev: true + + /array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + dev: true + + /array-includes@3.1.6: + resolution: {integrity: sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.1.4 + es-abstract: 1.21.1 + get-intrinsic: 1.2.4 + is-string: 1.0.7 + dev: true + + /array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true + + /array-union@3.0.1: + resolution: {integrity: sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==} + engines: {node: '>=12'} + dev: true + + /array.prototype.findlastindex@1.2.2: + resolution: {integrity: sha512-tb5thFFlUcp7NdNF6/MpDk/1r/4awWG1FIz3YqDf+/zJSTezBb+/5WViH41obXULHVpDzoiCLpJ/ZO9YbJMsdw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.1.4 + es-abstract: 1.21.1 + es-shim-unscopables: 1.0.0 + get-intrinsic: 1.2.4 + dev: true + + /array.prototype.flat@1.3.1: + resolution: {integrity: sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.1.4 + es-abstract: 1.21.1 + es-shim-unscopables: 1.0.0 + dev: true + + /array.prototype.flatmap@1.3.1: + resolution: {integrity: sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.1.4 + es-abstract: 1.21.1 + es-shim-unscopables: 1.0.0 + dev: true + + /arraybuffer.prototype.slice@1.0.1: + resolution: {integrity: sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.0 + call-bind: 1.0.7 + define-properties: 1.2.0 + get-intrinsic: 1.2.4 + is-array-buffer: 3.0.2 + is-shared-array-buffer: 1.0.2 + dev: true + + /arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + dev: true + + /assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + dev: true + + /ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + dependencies: + tslib: 2.5.0 + dev: true + + /astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + dev: true + + /async-sema@3.1.1: + resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} + dev: true + + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: true + + /autoprefixer@10.4.13(postcss@8.4.31): + resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + dependencies: + browserslist: 4.21.4 + caniuse-lite: 1.0.30001712 + fraction.js: 4.2.0 + normalize-range: 0.1.2 + picocolors: 1.0.0 + postcss: 8.4.31 + postcss-value-parser: 4.2.0 + dev: true + + /available-typed-arrays@1.0.5: + resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} + engines: {node: '>= 0.4'} + dev: true + + /axe-core@4.10.2: + resolution: {integrity: sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==} + engines: {node: '>=4'} + dev: true + + /axe-html-reporter@2.2.11(axe-core@4.10.2): + resolution: {integrity: sha512-WlF+xlNVgNVWiM6IdVrsh+N0Cw7qupe5HT9N6Uyi+aN7f6SSi92RDomiP1noW8OWIV85V6x404m5oKMeqRV3tQ==} + engines: {node: '>=8.9.0'} + peerDependencies: + axe-core: '>=3' + dependencies: + axe-core: 4.10.2 + mustache: 4.2.0 + dev: true + + /axe-playwright@2.0.3(playwright@1.52.0): + resolution: {integrity: sha512-s7iI2okyHHsD3XZK4RMJtTy2UASkNWLQtnzLuaHiK3AWkERf+cqZJqkxb7O4b56fnbib9YnZVRByTl92ME3o6g==} + peerDependencies: + playwright: '>1.0.0' + dependencies: + '@types/junit-report-builder': 3.0.2 + axe-core: 4.10.2 + axe-html-reporter: 2.2.11(axe-core@4.10.2) + junit-report-builder: 5.1.1 + picocolors: 1.1.0 + playwright: 1.52.0 + dev: true + + /axios@1.7.4: + resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==} + dependencies: + follow-redirects: 1.15.6 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: true + + /axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + /b4a@1.6.7: + resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} + dev: true + + /babel-jest@29.7.0(@babel/core@7.24.4): + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + dependencies: + '@babel/core': 7.24.4 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.24.4) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + dependencies: + '@babel/helper-plugin-utils': 7.24.7 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/template': 7.24.7 + '@babel/types': 7.26.9 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.20.6 + dev: true + + /babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.4): + resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.4) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.4) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.4) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.4) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.4) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.4) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.4) + dev: true + + /babel-preset-jest@29.6.3(@babel/core@7.24.4): + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.4 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.4) + dev: true + + /bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /balanced-match@2.0.0: + resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} + dev: true + + /bare-events@2.5.4: + resolution: {integrity: sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==} + requiresBuild: true + dev: true + optional: true + + /bare-fs@4.1.5: + resolution: {integrity: sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==} + engines: {bare: '>=1.16.0'} + requiresBuild: true + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + dependencies: + bare-events: 2.5.4 + bare-path: 3.0.0 + bare-stream: 2.6.5(bare-events@2.5.4) + dev: true + optional: true + + /bare-os@3.6.1: + resolution: {integrity: sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==} + engines: {bare: '>=1.14.0'} + requiresBuild: true + dev: true + optional: true + + /bare-path@3.0.0: + resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} + requiresBuild: true + dependencies: + bare-os: 3.6.1 + dev: true + optional: true + + /bare-stream@2.6.5(bare-events@2.5.4): + resolution: {integrity: sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==} + requiresBuild: true + peerDependencies: + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + bare-events: + optional: true + dependencies: + bare-events: 2.5.4 + streamx: 2.22.0 + dev: true + optional: true + + /better-opn@3.0.2: + resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} + engines: {node: '>=12.0.0'} + dependencies: + open: 8.4.2 + dev: true + + /big.js@5.2.2: + resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} + dev: true + + /bignumber.js@9.1.1: + resolution: {integrity: sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==} + dev: false + + /binary-extensions@2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true - /@types/flexsearch/0.7.3: - resolution: - { - integrity: sha512-HXwADeHEP4exXkCIwy2n1+i0f1ilP1ETQOH5KDOugjkTFZPntWo0Gr8stZOaebkxsdx+k0X/K6obU/+it07ocg==, - } + /bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + dependencies: + file-uri-to-path: 1.0.0 dev: true - /@types/form-data/0.0.33: - resolution: - { - integrity: sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==, - } + /body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} dependencies: - '@types/node': 17.0.41 + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.13.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} dev: true - /@types/fs-extra/9.0.13: - resolution: - { - integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==, - } + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: - '@types/node': 17.0.41 + balanced-match: 1.0.2 + concat-map: 0.0.1 dev: true - /@types/istanbul-lib-coverage/2.0.4: - resolution: - { - integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==, - } + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 dev: true - /@types/json-schema/7.0.11: - resolution: - { - integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==, - } + /braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.1.1 dev: true - /@types/linkify-it/3.0.2: - resolution: - { - integrity: sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==, - } + /browser-assert@1.2.1: + resolution: {integrity: sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==} dev: true - /@types/long/4.0.2: - resolution: - { - integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==, - } + /browserslist@4.21.4: + resolution: {integrity: sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001712 + electron-to-chromium: 1.4.745 + node-releases: 2.0.14 + update-browserslist-db: 1.0.13(browserslist@4.21.4) dev: true - /@types/markdown-it/12.2.3: - resolution: - { - integrity: sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==, - } + /browserslist@4.23.0: + resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true dependencies: - '@types/linkify-it': 3.0.2 - '@types/mdurl': 1.0.2 + caniuse-lite: 1.0.30001712 + electron-to-chromium: 1.4.745 + node-releases: 2.0.14 + update-browserslist-db: 1.0.13(browserslist@4.23.0) dev: true - /@types/mdurl/1.0.2: - resolution: - { - integrity: sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==, - } + /bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + dependencies: + node-int64: 0.4.0 dev: true - /@types/minimist/1.2.2: - resolution: - { - integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==, - } + /buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} dev: true - /@types/node/10.17.60: - resolution: - { - integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==, - } + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + /bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} dev: true - /@types/node/14.18.18: - resolution: - { - integrity: sha512-B9EoJFjhqcQ9OmQrNorItO+OwEOORNn3S31WuiHvZY/dm9ajkB7AKD/8toessEtHHNL+58jofbq7hMMY9v4yig==, - } + /c8@7.12.0: + resolution: {integrity: sha512-CtgQrHOkyxr5koX1wEUmN/5cfDa2ckbHRA4Gy5LAL0zaCFtVWJS5++n+w4/sr2GWGerBxgTjpKeDclk/Qk6W/A==} + engines: {node: '>=10.12.0'} + hasBin: true + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@istanbuljs/schema': 0.1.3 + find-up: 5.0.0 + foreground-child: 2.0.0 + istanbul-lib-coverage: 3.2.0 + istanbul-lib-report: 3.0.0 + istanbul-reports: 3.1.5 + rimraf: 3.0.2 + test-exclude: 6.0.0 + v8-to-istanbul: 9.0.1 + yargs: 16.2.0 + yargs-parser: 20.2.9 dev: true - /@types/node/16.11.39: - resolution: - { - integrity: sha512-K0MsdV42vPwm9L6UwhIxMAOmcvH/1OoVkZyCgEtVu4Wx7sElGloy/W7kMBNe/oJ7V/jW9BVt1F6RahH6e7tPXw==, - } + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} dev: true - /@types/node/17.0.41: - resolution: - { - integrity: sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw==, - } + /caching-transform@4.0.0: + resolution: {integrity: sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==} + engines: {node: '>=8'} + dependencies: + hasha: 5.2.2 + make-dir: 3.1.0 + package-hash: 4.0.0 + write-file-atomic: 3.0.3 dev: true - /@types/node/8.10.66: - resolution: - { - integrity: sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==, - } + /call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 dev: true - /@types/normalize-package-data/2.4.1: - resolution: - { - integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==, - } + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} dev: true - /@types/object-hash/1.3.4: - resolution: - { - integrity: sha512-xFdpkAkikBgqBdG9vIlsqffDV8GpvnPEzs0IUtr1v3BEB97ijsFQ4RXVbUZwjFThhB4MDSTUfvmxUD5PGx0wXA==, - } + /camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} dev: true - /@types/parse-json/4.0.0: - resolution: - { - integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==, - } + /camelcase-keys@7.0.2: + resolution: {integrity: sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==} + engines: {node: '>=12'} + dependencies: + camelcase: 6.3.0 + map-obj: 4.3.0 + quick-lru: 5.1.1 + type-fest: 1.4.0 dev: true - /@types/pug/2.0.6: - resolution: - { - integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==, - } + /camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} dev: true - /@types/qs/6.9.7: - resolution: - { - integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==, - } + /camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} dev: true - /@types/sanitize-html/2.6.2: - resolution: - { - integrity: sha512-7Lu2zMQnmHHQGKXVvCOhSziQMpa+R2hMHFefzbYoYMHeaXR0uXqNeOc3JeQQQ8/6Xa2Br/P1IQTLzV09xxAiUQ==, - } + /camelcase@8.0.0: + resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} + engines: {node: '>=16'} + requiresBuild: true + dev: false + optional: true + + /caniuse-api@3.0.0: + resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} dependencies: - htmlparser2: 6.1.0 + browserslist: 4.23.0 + caniuse-lite: 1.0.30001712 + lodash.memoize: 4.1.2 + lodash.uniq: 4.5.0 dev: true - /@types/sass/1.43.1: - resolution: - { - integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==, - } - dependencies: - '@types/node': 17.0.41 + /caniuse-lite@1.0.30001712: + resolution: {integrity: sha512-MBqPpGYYdQ7/hfKiet9SCI+nmN5/hp4ZzveOJubl5DTAMa5oggjAuoi0Z4onBpKPFI2ePGnQuQIzF3VxDjDJig==} dev: true - /@types/sinonjs__fake-timers/8.1.1: - resolution: - { - integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==, - } + /cargo-cp-artifact@0.1.8: + resolution: {integrity: sha512-3j4DaoTrsCD1MRkTF2Soacii0Nx7UHCce0EwUf4fHnggwiE4fbmF2AbnfzayR36DF8KGadfh7M/Yfy625kgPlA==} + hasBin: true dev: true - /@types/sizzle/2.3.3: - resolution: - { - integrity: sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==, - } + /ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + /chai@5.2.0: + resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} + engines: {node: '>=12'} + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.3 + pathval: 2.0.0 dev: true - /@types/yauzl/2.10.0: - resolution: - { - integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==, - } - requiresBuild: true + /chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} dependencies: - '@types/node': 17.0.41 + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 dev: true - optional: true - /@typescript-eslint/eslint-plugin/5.36.2_wuspq44aiwbrzu4wm6rgbxcdsu: - resolution: - { - integrity: sha512-OwwR8LRwSnI98tdc2z7mJYgY60gf7I9ZfGjN5EjCwwns9bdTuQfAXcsjSB2wSQ/TVNYSGKf4kzVXbNGaZvwiXw==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - peerDependencies: - '@typescript-eslint/parser': ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + /chalk@3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} dependencies: - '@typescript-eslint/parser': 5.36.2_pyvvhc3zqdua4akflcggygkl44 - '@typescript-eslint/scope-manager': 5.36.2 - '@typescript-eslint/type-utils': 5.36.2_pyvvhc3zqdua4akflcggygkl44 - '@typescript-eslint/utils': 5.36.2_pyvvhc3zqdua4akflcggygkl44 - debug: 4.3.4 - eslint: 8.23.0 - functional-red-black-tree: 1.0.1 - ignore: 5.2.0 - regexpp: 3.2.0 - semver: 7.3.7 - tsutils: 3.21.0_typescript@4.6.4 - typescript: 4.6.4 - transitivePeerDependencies: - - supports-color + ansi-styles: 4.3.0 + supports-color: 7.2.0 dev: true - /@typescript-eslint/parser/5.36.2_pyvvhc3zqdua4akflcggygkl44: - resolution: - { - integrity: sha512-qS/Kb0yzy8sR0idFspI9Z6+t7mqk/oRjnAYfewG+VN73opAUvmYL3oPIMmgOX6CnQS6gmVIXGshlb5RY/R22pA==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} dependencies: - '@typescript-eslint/scope-manager': 5.36.2 - '@typescript-eslint/types': 5.36.2 - '@typescript-eslint/typescript-estree': 5.36.2_typescript@4.6.4 - debug: 4.3.4 - eslint: 8.23.0 - typescript: 4.6.4 - transitivePeerDependencies: - - supports-color + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /chalk@5.2.0: + resolution: {integrity: sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: true + + /char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + dev: true + + /char-regex@2.0.1: + resolution: {integrity: sha512-oSvEeo6ZUD7NepqAat3RqoucZ5SeqLJgOvVIwkafu6IP3V0pO38s/ypdVUmDDK6qIIHNlYHJAKX9E7R7HoKElw==} + engines: {node: '>=12.20'} + dev: true + + /character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + /character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + /character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + /check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} dev: true - /@typescript-eslint/scope-manager/5.30.0: - resolution: - { - integrity: sha512-3TZxvlQcK5fhTBw5solQucWSJvonXf5yua5nx8OqK94hxdrT7/6W3/CS42MLd/f1BmlmmbGEgQcTHHCktUX5bQ==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} dependencies: - '@typescript-eslint/types': 5.30.0 - '@typescript-eslint/visitor-keys': 5.30.0 + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 dev: true - /@typescript-eslint/scope-manager/5.36.2: - resolution: - { - integrity: sha512-cNNP51L8SkIFSfce8B1NSUBTJTu2Ts4nWeWbFrdaqjmn9yKrAaJUBHkyTZc0cL06OFHpb+JZq5AUHROS398Orw==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + /chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} dependencies: - '@typescript-eslint/types': 5.36.2 - '@typescript-eslint/visitor-keys': 5.36.2 + readdirp: 4.1.2 + + /chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} dev: true - /@typescript-eslint/type-utils/5.36.2_pyvvhc3zqdua4akflcggygkl44: - resolution: - { - integrity: sha512-rPQtS5rfijUWLouhy6UmyNquKDPhQjKsaKH0WnY6hl/07lasj8gPaH2UD8xWkePn6SC+jW2i9c2DZVDnL+Dokw==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + /chromatic@11.22.2: + resolution: {integrity: sha512-Z7+9hD1yp1fUm34XX1wojIco0lQlXOVYhzDSE8v1ZU6qLD2r4N6UHKD+N+XY1Jj+gpsDFWYMTpSnDfcHZf5mhg==} + hasBin: true peerDependencies: - eslint: '*' - typescript: '*' + '@chromatic-com/cypress': ^0.*.* || ^1.0.0 + '@chromatic-com/playwright': ^0.*.* || ^1.0.0 peerDependenciesMeta: - typescript: + '@chromatic-com/cypress': + optional: true + '@chromatic-com/playwright': optional: true - dependencies: - '@typescript-eslint/typescript-estree': 5.36.2_typescript@4.6.4 - '@typescript-eslint/utils': 5.36.2_pyvvhc3zqdua4akflcggygkl44 - debug: 4.3.4 - eslint: 8.23.0 - tsutils: 3.21.0_typescript@4.6.4 - typescript: 4.6.4 - transitivePeerDependencies: - - supports-color dev: true - /@typescript-eslint/types/5.30.0: - resolution: - { - integrity: sha512-vfqcBrsRNWw/LBXyncMF/KrUTYYzzygCSsVqlZ1qGu1QtGs6vMkt3US0VNSQ05grXi5Yadp3qv5XZdYLjpp8ag==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + /chrome-trace-event@1.0.3: + resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==} + engines: {node: '>=6.0'} dev: true - /@typescript-eslint/types/5.36.2: - resolution: - { - integrity: sha512-9OJSvvwuF1L5eS2EQgFUbECb99F0mwq501w0H0EkYULkhFa19Qq7WFbycdw1PexAc929asupbZcgjVIe6OK/XQ==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + /ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} dev: true - /@typescript-eslint/typescript-estree/5.30.0_typescript@4.6.4: - resolution: - { - integrity: sha512-hDEawogreZB4n1zoqcrrtg/wPyyiCxmhPLpZ6kmWfKF5M5G0clRLaEexpuWr31fZ42F96SlD/5xCt1bT5Qm4Nw==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + /cjs-module-lexer@1.3.1: + resolution: {integrity: sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==} + dev: true + + /class-validator@0.14.2: + resolution: {integrity: sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==} + requiresBuild: true dependencies: - '@typescript-eslint/types': 5.30.0 - '@typescript-eslint/visitor-keys': 5.30.0 - debug: 4.3.4 - globby: 11.1.0 - is-glob: 4.0.3 - semver: 7.3.7 - tsutils: 3.21.0_typescript@4.6.4 - typescript: 4.6.4 - transitivePeerDependencies: - - supports-color + '@types/validator': 13.15.2 + libphonenumber-js: 1.12.9 + validator: 13.15.15 + dev: false + optional: true + + /class-variance-authority@0.7.0: + resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==} + dependencies: + clsx: 2.0.0 + dev: false + + /clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} dev: true - /@typescript-eslint/typescript-estree/5.36.2_typescript@4.6.4: - resolution: - { - integrity: sha512-8fyH+RfbKc0mTspfuEjlfqA4YywcwQK2Amcf6TDOwaRLg7Vwdu4bZzyvBZp4bjt1RRjQ5MDnOZahxMrt2l5v9w==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + /cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} dependencies: - '@typescript-eslint/types': 5.36.2 - '@typescript-eslint/visitor-keys': 5.36.2 - debug: 4.3.4 - globby: 11.1.0 - is-glob: 4.0.3 - semver: 7.3.7 - tsutils: 3.21.0_typescript@4.6.4 - typescript: 4.6.4 - transitivePeerDependencies: - - supports-color + restore-cursor: 3.1.0 dev: true - /@typescript-eslint/utils/5.30.0_pyvvhc3zqdua4akflcggygkl44: - resolution: - { - integrity: sha512-0bIgOgZflLKIcZsWvfklsaQTM3ZUbmtH0rJ1hKyV3raoUYyeZwcjQ8ZUJTzS7KnhNcsVT1Rxs7zeeMHEhGlltw==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + /cli-truncate@2.1.0: + resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} + engines: {node: '>=8'} dependencies: - '@types/json-schema': 7.0.11 - '@typescript-eslint/scope-manager': 5.30.0 - '@typescript-eslint/types': 5.30.0 - '@typescript-eslint/typescript-estree': 5.30.0_typescript@4.6.4 - eslint: 8.23.0 - eslint-scope: 5.1.1 - eslint-utils: 3.0.0_eslint@8.23.0 - transitivePeerDependencies: - - supports-color - - typescript + slice-ansi: 3.0.0 + string-width: 4.2.3 dev: true - /@typescript-eslint/utils/5.36.2_pyvvhc3zqdua4akflcggygkl44: - resolution: - { - integrity: sha512-uNcopWonEITX96v9pefk9DC1bWMdkweeSsewJ6GeC7L6j2t0SJywisgkr9wUTtXk90fi2Eljj90HSHm3OGdGRg==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + /cli-truncate@3.1.0: + resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: - '@types/json-schema': 7.0.11 - '@typescript-eslint/scope-manager': 5.36.2 - '@typescript-eslint/types': 5.36.2 - '@typescript-eslint/typescript-estree': 5.36.2_typescript@4.6.4 - eslint: 8.23.0 - eslint-scope: 5.1.1 - eslint-utils: 3.0.0_eslint@8.23.0 - transitivePeerDependencies: - - supports-color - - typescript + slice-ansi: 5.0.0 + string-width: 5.1.2 dev: true - /@typescript-eslint/visitor-keys/5.30.0: - resolution: - { - integrity: sha512-6WcIeRk2DQ3pHKxU1Ni0qMXJkjO/zLjBymlYBy/53qxe7yjEFSvzKLDToJjURUhSl2Fzhkl4SMXQoETauF74cw==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + /cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} dependencies: - '@typescript-eslint/types': 5.30.0 - eslint-visitor-keys: 3.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 dev: true - /@typescript-eslint/visitor-keys/5.36.2: - resolution: - { - integrity: sha512-BtRvSR6dEdrNt7Net2/XDjbYKU5Ml6GqJgVfXT0CxTCJlnIqK7rAGreuWKMT2t8cFUT2Msv5oxw0GMRD7T5J7A==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + /cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} dependencies: - '@typescript-eslint/types': 5.36.2 - eslint-visitor-keys: 3.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 dev: true - /@vercel/nft/0.22.1: - resolution: - { - integrity: sha512-lYYZIoxRurqDOSoVIdBicGnpUIpfyaS5qVjdPq+EfI285WqtZK3NK/dyCkiyBul+X2U2OEhRyeMdXPCHGJbohw==, - } - hasBin: true + /cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} dependencies: - '@mapbox/node-pre-gyp': 1.0.10 - acorn: 8.8.0 - async-sema: 3.1.1 - bindings: 1.5.0 - estree-walker: 2.0.2 - glob: 7.2.3 - graceful-fs: 4.2.10 - micromatch: 4.0.5 - node-gyp-build: 4.5.0 - resolve-from: 5.0.0 - rollup-pluginutils: 2.8.2 - transitivePeerDependencies: - - encoding - - supports-color + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 dev: true - /@vitest/ui/0.16.0: - resolution: - { - integrity: sha512-RwXYTFA2tVwUhuuTcdAaK5kIq+I3pnvNQUojPThZPZhS7ttKXkCgWwud0KXwnR04ofKc3HXEuzWPf6s7JD1vgw==, - } + /clsx@2.0.0: + resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==} + engines: {node: '>=6'} + dev: false + + /clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + /co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + dev: true + + /collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + dev: true + + /color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: - sirv: 2.0.2 + color-name: 1.1.3 dev: true - /@webassemblyjs/ast/1.11.1: - resolution: - { - integrity: sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==, - } + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} dependencies: - '@webassemblyjs/helper-numbers': 1.11.1 - '@webassemblyjs/helper-wasm-bytecode': 1.11.1 + color-name: 1.1.4 dev: true - /@webassemblyjs/floating-point-hex-parser/1.11.1: - resolution: - { - integrity: sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==, - } + /color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} dev: true - /@webassemblyjs/helper-api-error/1.11.1: - resolution: - { - integrity: sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==, - } + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true - /@webassemblyjs/helper-buffer/1.11.1: - resolution: - { - integrity: sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==, - } + /color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true dev: true - /@webassemblyjs/helper-numbers/1.11.1: - resolution: - { - integrity: sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==, - } - dependencies: - '@webassemblyjs/floating-point-hex-parser': 1.11.1 - '@webassemblyjs/helper-api-error': 1.11.1 - '@xtuc/long': 4.2.2 + /colord@2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} dev: true - /@webassemblyjs/helper-wasm-bytecode/1.11.1: - resolution: - { - integrity: sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==, - } + /colorette@2.0.19: + resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} dev: true - /@webassemblyjs/helper-wasm-section/1.11.1: - resolution: - { - integrity: sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==, - } + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} dependencies: - '@webassemblyjs/ast': 1.11.1 - '@webassemblyjs/helper-buffer': 1.11.1 - '@webassemblyjs/helper-wasm-bytecode': 1.11.1 - '@webassemblyjs/wasm-gen': 1.11.1 + delayed-stream: 1.0.0 dev: true - /@webassemblyjs/ieee754/1.11.1: - resolution: - { - integrity: sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==, - } - dependencies: - '@xtuc/ieee754': 1.2.0 + /comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + /commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} dev: true - /@webassemblyjs/leb128/1.11.1: - resolution: - { - integrity: sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==, - } - dependencies: - '@xtuc/long': 4.2.2 + /commander@3.0.2: + resolution: {integrity: sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==} dev: true - /@webassemblyjs/utf8/1.11.1: - resolution: - { - integrity: sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==, - } + /commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} dev: true - /@webassemblyjs/wasm-edit/1.11.1: - resolution: - { - integrity: sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==, - } - dependencies: - '@webassemblyjs/ast': 1.11.1 - '@webassemblyjs/helper-buffer': 1.11.1 - '@webassemblyjs/helper-wasm-bytecode': 1.11.1 - '@webassemblyjs/helper-wasm-section': 1.11.1 - '@webassemblyjs/wasm-gen': 1.11.1 - '@webassemblyjs/wasm-opt': 1.11.1 - '@webassemblyjs/wasm-parser': 1.11.1 - '@webassemblyjs/wast-printer': 1.11.1 + /commander@5.1.0: + resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} + engines: {node: '>= 6'} dev: true - /@webassemblyjs/wasm-gen/1.11.1: - resolution: - { - integrity: sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==, - } - dependencies: - '@webassemblyjs/ast': 1.11.1 - '@webassemblyjs/helper-wasm-bytecode': 1.11.1 - '@webassemblyjs/ieee754': 1.11.1 - '@webassemblyjs/leb128': 1.11.1 - '@webassemblyjs/utf8': 1.11.1 + /commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} dev: true - /@webassemblyjs/wasm-opt/1.11.1: - resolution: - { - integrity: sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==, - } - dependencies: - '@webassemblyjs/ast': 1.11.1 - '@webassemblyjs/helper-buffer': 1.11.1 - '@webassemblyjs/wasm-gen': 1.11.1 - '@webassemblyjs/wasm-parser': 1.11.1 + /commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} dev: true - /@webassemblyjs/wasm-parser/1.11.1: - resolution: - { - integrity: sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==, - } - dependencies: - '@webassemblyjs/ast': 1.11.1 - '@webassemblyjs/helper-api-error': 1.11.1 - '@webassemblyjs/helper-wasm-bytecode': 1.11.1 - '@webassemblyjs/ieee754': 1.11.1 - '@webassemblyjs/leb128': 1.11.1 - '@webassemblyjs/utf8': 1.11.1 + /commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + dev: true + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true - /@webassemblyjs/wast-printer/1.11.1: - resolution: - { - integrity: sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==, - } + /console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + dev: true + + /content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} dependencies: - '@webassemblyjs/ast': 1.11.1 - '@xtuc/long': 4.2.2 + safe-buffer: 5.2.1 dev: true - /@xtuc/ieee754/1.2.0: - resolution: - { - integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==, - } + /content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} dev: true - /@xtuc/long/4.2.2: - resolution: - { - integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==, - } + /convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} dev: true - /abab/2.0.6: - resolution: - { - integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==, - } + /convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} dev: true - /abbrev/1.1.1: - resolution: - { - integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==, - } + /cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} dev: true - /acorn-globals/6.0.0: - resolution: - { - integrity: sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==, - } + /cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + + /cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} dependencies: - acorn: 7.4.1 - acorn-walk: 7.2.0 + object-assign: 4.1.1 + vary: 1.1.2 dev: true - /acorn-import-assertions/1.8.0_acorn@8.7.1: - resolution: - { - integrity: sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==, - } - peerDependencies: - acorn: ^8 + /cosmiconfig@8.2.0: + resolution: {integrity: sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==} + engines: {node: '>=14'} + dependencies: + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + dev: true + + /create-jest@29.7.0(@types/node@18.15.3): + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true dependencies: - acorn: 8.7.1 + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@18.15.3) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: true + + /cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 dev: true - /acorn-jsx/5.3.2_acorn@8.8.0: - resolution: - { - integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, - } + /css-declaration-sorter@6.3.1(postcss@8.4.31): + resolution: {integrity: sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w==} + engines: {node: ^10 || ^12 || >=14} peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + postcss: ^8.0.9 dependencies: - acorn: 8.8.0 + postcss: 8.4.31 + dev: true + + /css-functions-list@3.2.0: + resolution: {integrity: sha512-d/jBMPyYybkkLVypgtGv12R+pIFw4/f/IHtCTxWpZc8ofTYOPigIgmA6vu5rMHartZC+WuXhBUHfnyNUIQSYrg==} + engines: {node: '>=12.22'} dev: true - /acorn-node/1.8.2: - resolution: - { - integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==, - } + /css-select@4.3.0: + resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} dependencies: - acorn: 7.4.1 - acorn-walk: 7.2.0 - xtend: 4.0.2 + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 4.3.1 + domutils: 2.8.0 + nth-check: 2.1.1 dev: true - /acorn-walk/7.2.0: - resolution: - { - integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==, - } - engines: { node: '>=0.4.0' } + /css-tree@1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 dev: true - /acorn-walk/8.2.0: - resolution: - { - integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==, - } - engines: { node: '>=0.4.0' } + /css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.2.1 dev: true - /acorn/7.4.1: - resolution: - { - integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==, - } - engines: { node: '>=0.4.0' } - hasBin: true + /css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} dev: true - /acorn/8.7.1: - resolution: - { - integrity: sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==, - } - engines: { node: '>=0.4.0' } - hasBin: true + /css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} dev: true - /acorn/8.8.0: - resolution: - { - integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==, - } - engines: { node: '>=0.4.0' } + /cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} hasBin: true dev: true - /agent-base/6.0.2: - resolution: - { - integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==, - } - engines: { node: '>= 6.0.0' } + /cssnano-preset-default@5.2.13(postcss@8.4.31): + resolution: {integrity: sha512-PX7sQ4Pb+UtOWuz8A1d+Rbi+WimBIxJTRyBdgGp1J75VU0r/HFQeLnMYgHiCAp6AR4rqrc7Y4R+1Rjk3KJz6DQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 dependencies: - debug: 4.3.4 - transitivePeerDependencies: - - supports-color + css-declaration-sorter: 6.3.1(postcss@8.4.31) + cssnano-utils: 3.1.0(postcss@8.4.31) + postcss: 8.4.31 + postcss-calc: 8.2.4(postcss@8.4.31) + postcss-colormin: 5.3.0(postcss@8.4.31) + postcss-convert-values: 5.1.3(postcss@8.4.31) + postcss-discard-comments: 5.1.2(postcss@8.4.31) + postcss-discard-duplicates: 5.1.0(postcss@8.4.31) + postcss-discard-empty: 5.1.1(postcss@8.4.31) + postcss-discard-overridden: 5.1.0(postcss@8.4.31) + postcss-merge-longhand: 5.1.7(postcss@8.4.31) + postcss-merge-rules: 5.1.3(postcss@8.4.31) + postcss-minify-font-values: 5.1.0(postcss@8.4.31) + postcss-minify-gradients: 5.1.1(postcss@8.4.31) + postcss-minify-params: 5.1.4(postcss@8.4.31) + postcss-minify-selectors: 5.2.1(postcss@8.4.31) + postcss-normalize-charset: 5.1.0(postcss@8.4.31) + postcss-normalize-display-values: 5.1.0(postcss@8.4.31) + postcss-normalize-positions: 5.1.1(postcss@8.4.31) + postcss-normalize-repeat-style: 5.1.1(postcss@8.4.31) + postcss-normalize-string: 5.1.0(postcss@8.4.31) + postcss-normalize-timing-functions: 5.1.0(postcss@8.4.31) + postcss-normalize-unicode: 5.1.1(postcss@8.4.31) + postcss-normalize-url: 5.1.0(postcss@8.4.31) + postcss-normalize-whitespace: 5.1.1(postcss@8.4.31) + postcss-ordered-values: 5.1.3(postcss@8.4.31) + postcss-reduce-initial: 5.1.1(postcss@8.4.31) + postcss-reduce-transforms: 5.1.0(postcss@8.4.31) + postcss-svgo: 5.1.0(postcss@8.4.31) + postcss-unique-selectors: 5.1.1(postcss@8.4.31) + dev: true + + /cssnano-utils@3.1.0(postcss@8.4.31): + resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.31 dev: true - /aggregate-error/3.1.0: - resolution: - { - integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==, - } - engines: { node: '>=8' } + /cssnano@5.1.14(postcss@8.4.31): + resolution: {integrity: sha512-Oou7ihiTocbKqi0J1bB+TRJIQX5RMR3JghA8hcWSw9mjBLQ5Y3RWqEDoYG3sRNlAbCIXpqMoZGbq5KDR3vdzgw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 dependencies: - clean-stack: 2.2.0 - indent-string: 4.0.0 + cssnano-preset-default: 5.2.13(postcss@8.4.31) + lilconfig: 2.0.6 + postcss: 8.4.31 + yaml: 1.10.2 dev: true - /ajv-keywords/3.5.2_ajv@6.12.6: - resolution: - { - integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==, - } - peerDependencies: - ajv: ^6.9.1 + /csso@4.2.0: + resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} + engines: {node: '>=8.0.0'} dependencies: - ajv: 6.12.6 + css-tree: 1.1.3 + dev: true + + /cssom@0.3.8: + resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} dev: true - /ajv/6.12.6: - resolution: - { - integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==, - } - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 + /cssom@0.5.0: + resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} dev: true - /ajv/8.11.0: - resolution: - { - integrity: sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==, - } + /cssstyle@2.3.0: + resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} + engines: {node: '>=8'} dependencies: - fast-deep-equal: 3.1.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - uri-js: 4.4.1 + cssom: 0.3.8 dev: true - /ansi-colors/4.1.3: - resolution: - { - integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==, - } - engines: { node: '>=6' } + /csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} dev: true - /ansi-escapes/4.3.2: - resolution: - { - integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==, - } - engines: { node: '>=8' } + /cwd@0.10.0: + resolution: {integrity: sha512-YGZxdTTL9lmLkCUTpg4j0zQ7IhRB5ZmqNBbGCl3Tg6MP/d5/6sY7L5mmTjzbc6JKgVZYiqTQTNhPFsbXNGlRaA==} + engines: {node: '>=0.8'} dependencies: - type-fest: 0.21.3 + find-pkg: 0.1.2 + fs-exists-sync: 0.1.0 dev: true - /ansi-regex/5.0.1: - resolution: - { - integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, - } - engines: { node: '>=8' } + /data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} dev: true - /ansi-regex/6.0.1: - resolution: - { - integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==, - } - engines: { node: '>=12' } + /data-urls@3.0.2: + resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} + engines: {node: '>=12'} + dependencies: + abab: 2.0.6 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 dev: true - /ansi-styles/3.2.1: - resolution: - { - integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==, - } - engines: { node: '>=4' } + /date-fns-tz@1.3.7(date-fns@2.29.3): + resolution: {integrity: sha512-1t1b8zyJo+UI8aR+g3iqr5fkUHWpd58VBx8J/ZSQ+w7YrGlw80Ag4sA86qkfCXRBLmMc4I2US+aPMd4uKvwj5g==} + peerDependencies: + date-fns: '>=2.0.0' dependencies: - color-convert: 1.9.3 - dev: true + date-fns: 2.29.3 + dev: false + + /date-fns@2.29.3: + resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==} + engines: {node: '>=0.11'} + dev: false + + /dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + requiresBuild: true + dev: false + optional: true - /ansi-styles/4.3.0: - resolution: - { - integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, - } - engines: { node: '>=8' } + /debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true dependencies: - color-convert: 2.0.1 + ms: 2.0.0 dev: true - /ansi-styles/5.2.0: - resolution: - { - integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==, - } - engines: { node: '>=10' } + /debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 dev: true - /ansi-styles/6.1.0: - resolution: - { - integrity: sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==, - } - engines: { node: '>=12' } + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 dev: true - /anymatch/3.1.2: - resolution: - { - integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==, - } - engines: { node: '>= 8' } + /debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.3 + + /decamelize-keys@1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 + decamelize: 1.2.0 + map-obj: 1.0.1 + dev: true + + /decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} dev: true - /aproba/2.0.0: - resolution: - { - integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==, - } + /decamelize@5.0.1: + resolution: {integrity: sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==} + engines: {node: '>=10'} dev: true - /arch/2.2.0: - resolution: - { - integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==, - } + /decimal.js@10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} dev: true - /are-we-there-yet/2.0.0: - resolution: - { - integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==, - } - engines: { node: '>=10' } + /decode-named-character-reference@1.0.2: + resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} dependencies: - delegates: 1.0.0 - readable-stream: 3.6.0 + character-entities: 2.0.2 + + /dedent-js@1.0.1: + resolution: {integrity: sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==} + + /dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true dev: true - /arg/4.1.3: - resolution: - { - integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==, - } + /deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} dev: true - /arg/5.0.2: - resolution: - { - integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==, - } + /deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true - /argparse/1.0.10: - resolution: - { - integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==, - } + /deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + /default-require-extensions@3.0.1: + resolution: {integrity: sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==} + engines: {node: '>=8'} dependencies: - sprintf-js: 1.0.3 + strip-bom: 4.0.0 dev: true - /argparse/2.0.1: - resolution: - { - integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, - } + /define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.0.1 dev: true - /array-union/2.1.0: - resolution: - { - integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==, - } - engines: { node: '>=8' } + /define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} dev: true - /array-union/3.0.1: - resolution: - { - integrity: sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==, - } - engines: { node: '>=12' } + /define-properties@1.1.4: + resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} + engines: {node: '>= 0.4'} + dependencies: + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 dev: true - /arrify/1.0.1: - resolution: - { - integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==, - } - engines: { node: '>=0.10.0' } + /define-properties@1.2.0: + resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} + engines: {node: '>= 0.4'} + dependencies: + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 dev: true - /asap/2.0.6: - resolution: - { - integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==, - } + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} dev: true - /asn1/0.2.6: - resolution: - { - integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==, - } - dependencies: - safer-buffer: 2.1.2 + /delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} dev: true - /assert-plus/1.0.0: - resolution: - { - integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==, - } - engines: { node: '>=0.8' } + /depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} dev: true - /assertion-error/1.1.0: - resolution: - { - integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==, - } + /dependency-graph@0.11.0: + resolution: {integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==} + engines: {node: '>= 0.6.0'} dev: true - /astral-regex/2.0.0: - resolution: - { - integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==, - } - engines: { node: '>=8' } - dev: true + /dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} - /async-sema/3.1.1: - resolution: - { - integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==, - } + /destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} dev: true - /async/3.2.3: - resolution: - { - integrity: sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==, - } + /detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} dev: true - /asynckit/0.4.0: - resolution: - { - integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==, - } + /detect-libc@2.0.1: + resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} + engines: {node: '>=8'} dev: true - /at-least-node/1.0.0: - resolution: - { - integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==, - } - engines: { node: '>= 4.0.0' } + /detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} dev: true - /autoprefixer/10.4.7_postcss@8.4.14: - resolution: - { - integrity: sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA==, - } - engines: { node: ^10 || ^12 || >=14 } - hasBin: true - peerDependencies: - postcss: ^8.1.0 + /devalue@5.1.1: + resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} + + /devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} dependencies: - browserslist: 4.20.3 - caniuse-lite: 1.0.30001341 - fraction.js: 4.2.0 - normalize-range: 0.1.2 - picocolors: 1.0.0 - postcss: 8.4.14 - postcss-value-parser: 4.2.0 - dev: true + dequal: 2.0.3 + dev: false - /aws-sign2/0.7.0: - resolution: - { - integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==, - } + /didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dev: true - /aws4/1.11.0: - resolution: - { - integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==, - } + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true - /babel-loader/8.2.5_upeuvotedl3gkw7tmcpzrrrck4: - resolution: - { - integrity: sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==, - } - engines: { node: '>= 8.9' } - peerDependencies: - '@babel/core': ^7.0.0 - webpack: '>=2' - dependencies: - '@babel/core': 7.17.12 - find-cache-dir: 3.3.2 - loader-utils: 2.0.2 - make-dir: 3.1.0 - schema-utils: 2.7.1 - webpack: 5.73.0_esbuild@0.13.15 + /diff@5.1.0: + resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} + engines: {node: '>=0.3.1'} dev: true - /babel-plugin-transform-vite-meta-env/1.0.3: - resolution: - { - integrity: sha512-eyfuDEXrMu667TQpmctHeTlJrZA6jXYHyEJFjcM0yEa60LS/LXlOg2PBbMb8DVS+V9CnTj/j9itdlDVMcY2zEg==, - } + /diffable-html@4.1.0: + resolution: {integrity: sha512-++kyNek+YBLH8cLXS+iTj/Hiy2s5qkRJEJ8kgu/WHbFrVY2vz9xPFUT+fii2zGF0m1CaojDlQJjkfrCt7YWM1g==} dependencies: - '@babel/runtime': 7.17.9 - '@types/babel__core': 7.1.19 + htmlparser2: 3.10.1 dev: true - /babel-plugin-transform-vite-meta-glob/1.0.3: - resolution: - { - integrity: sha512-JW3VnwUjJqpj0FM0vJFxrGdxSBcHOa0j5YMvvtXYPmFshroq53nbK9dqRETgjXlMrfIz0oU/6ki+u1GdVWdNHA==, - } + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} dependencies: - '@babel/runtime': 7.17.9 - '@types/babel__core': 7.1.19 - glob: 7.2.3 + path-type: 4.0.0 dev: true - /babel-preset-vite/1.0.4: - resolution: - { - integrity: sha512-RZS/wNfEUD8aMliObxqlPw4ZR7R5OsT1G2IHd5nuUmiYKS6zemur8aZ5WPbfQwPpTPe9VEjcrxQA/6PKBWRTkg==, - } + /dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + /doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} dependencies: - '@babel/runtime': 7.17.9 - '@types/babel__core': 7.1.19 - babel-plugin-transform-vite-meta-env: 1.0.3 - babel-plugin-transform-vite-meta-glob: 1.0.3 + esutils: 2.0.3 dev: true - /balanced-match/1.0.2: - resolution: - { - integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, - } + /doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dependencies: + esutils: 2.0.3 dev: true - /balanced-match/2.0.0: - resolution: - { - integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==, - } + /dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} dev: true - /base64-js/1.5.1: - resolution: - { - integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==, - } + /dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} dev: true - /bcrypt-pbkdf/1.0.2: - resolution: - { - integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==, - } + /dom-serializer@0.2.2: + resolution: {integrity: sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==} dependencies: - tweetnacl: 0.14.5 - dev: true - - /big.js/5.2.2: - resolution: - { - integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==, - } + domelementtype: 2.3.0 + entities: 2.2.0 dev: true - /bignumber.js/9.1.0: - resolution: - { - integrity: sha512-4LwHK4nfDOraBCtst+wOWIHbu1vhvAPJK8g8nROd4iuc3PSEjWif/qwbkh8jwCJz6yDBvtU4KPynETgrfh7y3A==, - } - dev: false - - /binary-extensions/2.2.0: - resolution: - { - integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==, - } - engines: { node: '>=8' } + /dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 dev: true - /bindings/1.5.0: - resolution: - { - integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==, - } + /dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} dependencies: - file-uri-to-path: 1.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.4.0 dev: true - /birpc/0.1.1: - resolution: - { - integrity: sha512-B64AGL4ug2IS2jvV/zjTYDD1L+2gOJTT7Rv+VaK7KVQtQOo/xZbCDsh7g727ipckmU+QJYRqo5RcifVr0Kgcmg==, - } + /domelementtype@1.3.1: + resolution: {integrity: sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==} dev: true - /blob-util/2.0.2: - resolution: - { - integrity: sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==, - } + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} dev: true - /bluebird/3.7.2: - resolution: - { - integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==, - } + /domexception@4.0.0: + resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} + engines: {node: '>=12'} + dependencies: + webidl-conversions: 7.0.0 dev: true - /boolbase/1.0.0: - resolution: - { - integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==, - } + /domhandler@2.4.2: + resolution: {integrity: sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==} + dependencies: + domelementtype: 1.3.1 dev: true - /brace-expansion/1.1.11: - resolution: - { - integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==, - } + /domhandler@3.3.0: + resolution: {integrity: sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==} + engines: {node: '>= 4'} dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 + domelementtype: 2.3.0 dev: true - /braces/3.0.2: - resolution: - { - integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==, - } - engines: { node: '>=8' } + /domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} dependencies: - fill-range: 7.0.1 + domelementtype: 2.3.0 dev: true - /browser-process-hrtime/1.0.0: - resolution: - { - integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==, - } + /domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 dev: true - /browserslist/4.20.3: - resolution: - { - integrity: sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==, - } - engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } - hasBin: true + /domutils@1.7.0: + resolution: {integrity: sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==} dependencies: - caniuse-lite: 1.0.30001341 - electron-to-chromium: 1.4.137 - escalade: 3.1.1 - node-releases: 2.0.4 - picocolors: 1.0.0 + dom-serializer: 0.2.2 + domelementtype: 1.3.1 dev: true - /buffer-crc32/0.2.13: - resolution: - { - integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==, - } + /domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 dev: true - /buffer-from/1.1.2: - resolution: - { - integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==, - } + /domutils@3.0.1: + resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==} + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 dev: true - /buffer/5.7.1: - resolution: - { - integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==, - } - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 + /duplexer@0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} dev: true - /c8/7.11.3: - resolution: - { - integrity: sha512-6YBmsaNmqRm9OS3ZbIiL2EZgi1+Xc4O24jL3vMYGE6idixYuGdy76rIfIdltSKDj9DpLNrcXSonUTR1miBD0wA==, - } - engines: { node: '>=10.12.0' } - hasBin: true - dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@istanbuljs/schema': 0.1.3 - find-up: 5.0.0 - foreground-child: 2.0.0 - istanbul-lib-coverage: 3.2.0 - istanbul-lib-report: 3.0.0 - istanbul-reports: 3.1.4 - rimraf: 3.0.2 - test-exclude: 6.0.0 - v8-to-istanbul: 9.0.1 - yargs: 16.2.0 - yargs-parser: 20.2.9 + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true - /cachedir/2.3.0: - resolution: - { - integrity: sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==, - } - engines: { node: '>=6' } + /ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} dev: true - /call-bind/1.0.2: - resolution: - { - integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==, - } + /effect@3.16.10: + resolution: {integrity: sha512-F0hDCOLax7i3SOy5wQnJZTzKP9aGg8OQNUJ2s8YoL5fVcCgRCgE+Ky+Hfz5qC7LgVVOWDDbRpCh5MPB20wRI8Q==} + requiresBuild: true dependencies: - function-bind: 1.1.1 - get-intrinsic: 1.1.1 + '@standard-schema/spec': 1.0.0 + fast-check: 3.23.2 dev: false + optional: true - /callsites/3.1.0: - resolution: - { - integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, - } - engines: { node: '>=6' } + /electron-to-chromium@1.4.745: + resolution: {integrity: sha512-tRbzkaRI5gbUn5DEvF0dV4TQbMZ5CLkWeTAXmpC9IrYT+GE+x76i9p+o3RJ5l9XmdQlI1pPhVtE9uNcJJ0G0EA==} dev: true - /camel-case/4.1.2: - resolution: - { - integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==, - } - dependencies: - pascal-case: 3.1.2 - tslib: 2.4.0 + /emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} dev: true - /camelcase-css/2.0.1: - resolution: - { - integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==, - } - engines: { node: '>= 6' } + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} dev: true - /camelcase-keys/6.2.2: - resolution: - { - integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==, - } - engines: { node: '>=8' } - dependencies: - camelcase: 5.3.1 - map-obj: 4.3.0 - quick-lru: 4.0.1 + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} dev: true - /camelcase/5.3.1: - resolution: - { - integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==, - } - engines: { node: '>=6' } + /emojis-list@3.0.0: + resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} + engines: {node: '>= 4'} dev: true - /caniuse-api/3.0.0: - resolution: - { - integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==, - } - dependencies: - browserslist: 4.20.3 - caniuse-lite: 1.0.30001341 - lodash.memoize: 4.1.2 - lodash.uniq: 4.5.0 + /encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} dev: true - /caniuse-lite/1.0.30001341: - resolution: - { - integrity: sha512-2SodVrFFtvGENGCv0ChVJIDQ0KPaS1cg7/qtfMaICgeMolDdo/Z2OD32F0Aq9yl6F4YFwGPBS5AaPqNYiW4PoA==, - } + /encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} dev: true - /capital-case/1.0.4: - resolution: - { - integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==, - } + /end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: - no-case: 3.0.4 - tslib: 2.4.0 - upper-case-first: 2.0.2 + once: 1.4.0 dev: true - /caseless/0.12.0: - resolution: - { - integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==, - } + /enhanced-resolve@5.17.1: + resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} + engines: {node: '>=10.13.0'} + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 dev: true - /chai/4.3.6: - resolution: - { - integrity: sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==, - } - engines: { node: '>=4' } + /enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} dependencies: - assertion-error: 1.1.0 - check-error: 1.0.2 - deep-eql: 3.0.1 - get-func-name: 2.0.0 - loupe: 2.3.4 - pathval: 1.1.1 - type-detect: 4.0.8 + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 dev: true - /chalk/2.4.2: - resolution: - { - integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==, - } - engines: { node: '>=4' } - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 + /entities@1.1.2: + resolution: {integrity: sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==} dev: true - /chalk/4.1.2: - resolution: - { - integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, - } - engines: { node: '>=10' } - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 + /entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} dev: true - /change-case/4.1.2: - resolution: - { - integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==, - } + /entities@4.4.0: + resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==} + engines: {node: '>=0.12'} + + /error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: - camel-case: 4.1.2 - capital-case: 1.0.4 - constant-case: 3.0.4 - dot-case: 3.0.4 - header-case: 2.0.4 - no-case: 3.0.4 - param-case: 3.0.4 - pascal-case: 3.1.2 - path-case: 3.0.4 - sentence-case: 3.0.4 - snake-case: 3.0.4 - tslib: 2.4.0 + is-arrayish: 0.2.1 dev: true - /check-error/1.0.2: - resolution: - { - integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==, - } + /es-abstract@1.21.1: + resolution: {integrity: sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.7 + es-set-tostringtag: 2.0.1 + es-to-primitive: 1.2.1 + function-bind: 1.1.2 + function.prototype.name: 1.1.5 + get-intrinsic: 1.2.4 + get-symbol-description: 1.0.0 + globalthis: 1.0.3 + gopd: 1.0.1 + has: 1.0.3 + has-property-descriptors: 1.0.2 + has-proto: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.4 + is-array-buffer: 3.0.1 + is-callable: 1.2.7 + is-negative-zero: 2.0.2 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-typed-array: 1.1.10 + is-weakref: 1.0.2 + object-inspect: 1.13.2 + object-keys: 1.1.1 + object.assign: 4.1.4 + regexp.prototype.flags: 1.4.3 + safe-regex-test: 1.0.0 + string.prototype.trimend: 1.0.6 + string.prototype.trimstart: 1.0.6 + typed-array-length: 1.0.4 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.11 dev: true - /check-more-types/2.24.0: - resolution: - { - integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==, - } - engines: { node: '>= 0.8.0' } + /es-abstract@1.22.1: + resolution: {integrity: sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.0 + arraybuffer.prototype.slice: 1.0.1 + available-typed-arrays: 1.0.5 + call-bind: 1.0.7 + es-set-tostringtag: 2.0.1 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.5 + get-intrinsic: 1.2.4 + get-symbol-description: 1.0.0 + globalthis: 1.0.3 + gopd: 1.0.1 + has: 1.0.3 + has-property-descriptors: 1.0.2 + has-proto: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.5 + is-array-buffer: 3.0.2 + is-callable: 1.2.7 + is-negative-zero: 2.0.2 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-typed-array: 1.1.10 + is-weakref: 1.0.2 + object-inspect: 1.13.2 + object-keys: 1.1.1 + object.assign: 4.1.4 + regexp.prototype.flags: 1.5.0 + safe-array-concat: 1.0.0 + safe-regex-test: 1.0.0 + string.prototype.trim: 1.2.7 + string.prototype.trimend: 1.0.6 + string.prototype.trimstart: 1.0.6 + typed-array-buffer: 1.0.0 + typed-array-byte-length: 1.0.0 + typed-array-byte-offset: 1.0.0 + typed-array-length: 1.0.4 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.11 dev: true - /chnl/1.2.0: - resolution: - { - integrity: sha512-g5gJb59edwCliFbX2j7G6sBfY4sX9YLy211yctONI2GRaiX0f2zIbKWmBm+sPqFNEpM7Ljzm7IJX/xrjiEbPrw==, - } - dev: false - - /chokidar/3.5.3: - resolution: - { - integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==, - } - engines: { node: '>= 8.10.0' } + /es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} dependencies: - anymatch: 3.1.2 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.2 + get-intrinsic: 1.2.4 dev: true - /chownr/2.0.0: - resolution: - { - integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==, - } - engines: { node: '>=10' } + /es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} dev: true - /chrome-trace-event/1.0.3: - resolution: - { - integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==, - } - engines: { node: '>=6.0' } + /es-module-lexer@1.6.0: + resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} dev: true - /ci-info/3.3.1: - resolution: - { - integrity: sha512-SXgeMX9VwDe7iFFaEWkA5AstuER9YKqy4EhHqr4DVqkwmD9rpVimkMKWHdjn30Ja45txyjhSn63lVX69eVCckg==, - } + /es-set-tostringtag@2.0.1: + resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.4 + has: 1.0.3 + has-tostringtag: 1.0.0 dev: true - /clean-stack/2.2.0: - resolution: - { - integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==, - } - engines: { node: '>=6' } + /es-shim-unscopables@1.0.0: + resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} + dependencies: + has: 1.0.3 dev: true - /cli-cursor/3.1.0: - resolution: - { - integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==, - } - engines: { node: '>=8' } + /es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} dependencies: - restore-cursor: 3.1.0 + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 dev: true - /cli-table3/0.6.2: - resolution: - { - integrity: sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw==, - } - engines: { node: 10.* || >= 12.* } - dependencies: - string-width: 4.2.3 - optionalDependencies: - '@colors/colors': 1.5.0 + /es-toolkit@1.32.0: + resolution: {integrity: sha512-ZfSfHP1l6ubgW/B/FRtqb9bYdMvI6jizbOSfbwwJNcOQ1QE6TFsC3jpQkZ900uUPSR3t3SU5Ds7UWKnYz+uP8Q==} dev: true - /cli-truncate/2.1.0: - resolution: - { - integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==, - } - engines: { node: '>=8' } - dependencies: - slice-ansi: 3.0.0 - string-width: 4.2.3 + /es6-error@4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} dev: true - /cli-truncate/3.1.0: - resolution: - { - integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==, - } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } - dependencies: - slice-ansi: 5.0.0 - string-width: 5.1.2 + /es6-promise@3.3.1: + resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} dev: true - /cliui/7.0.4: - resolution: - { - integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==, - } + /esbuild-register@3.5.0(esbuild@0.25.0): + resolution: {integrity: sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==} + peerDependencies: + esbuild: '>=0.12 <1' dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 + debug: 4.4.0 + esbuild: 0.25.0 + transitivePeerDependencies: + - supports-color dev: true - /clone-regexp/2.2.0: - resolution: - { - integrity: sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==, - } - engines: { node: '>=6' } + /esbuild-runner@2.2.2(esbuild@0.25.0): + resolution: {integrity: sha512-fRFVXcmYVmSmtYm2mL8RlUASt2TDkGh3uRcvHFOKNr/T58VrfVeKD9uT9nlgxk96u0LS0ehS/GY7Da/bXWKkhw==} + hasBin: true + requiresBuild: true + peerDependencies: + esbuild: '*' dependencies: - is-regexp: 2.1.0 - dev: true + esbuild: 0.25.0 + source-map-support: 0.5.21 + tslib: 2.4.0 + dev: false + optional: true - /color-convert/1.9.3: - resolution: - { - integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==, - } + /esbuild@0.17.19: + resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.17.19 + '@esbuild/android-arm64': 0.17.19 + '@esbuild/android-x64': 0.17.19 + '@esbuild/darwin-arm64': 0.17.19 + '@esbuild/darwin-x64': 0.17.19 + '@esbuild/freebsd-arm64': 0.17.19 + '@esbuild/freebsd-x64': 0.17.19 + '@esbuild/linux-arm': 0.17.19 + '@esbuild/linux-arm64': 0.17.19 + '@esbuild/linux-ia32': 0.17.19 + '@esbuild/linux-loong64': 0.17.19 + '@esbuild/linux-mips64el': 0.17.19 + '@esbuild/linux-ppc64': 0.17.19 + '@esbuild/linux-riscv64': 0.17.19 + '@esbuild/linux-s390x': 0.17.19 + '@esbuild/linux-x64': 0.17.19 + '@esbuild/netbsd-x64': 0.17.19 + '@esbuild/openbsd-x64': 0.17.19 + '@esbuild/sunos-x64': 0.17.19 + '@esbuild/win32-arm64': 0.17.19 + '@esbuild/win32-ia32': 0.17.19 + '@esbuild/win32-x64': 0.17.19 + dev: true + + /esbuild@0.19.12: + resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.19.12 + '@esbuild/android-arm': 0.19.12 + '@esbuild/android-arm64': 0.19.12 + '@esbuild/android-x64': 0.19.12 + '@esbuild/darwin-arm64': 0.19.12 + '@esbuild/darwin-x64': 0.19.12 + '@esbuild/freebsd-arm64': 0.19.12 + '@esbuild/freebsd-x64': 0.19.12 + '@esbuild/linux-arm': 0.19.12 + '@esbuild/linux-arm64': 0.19.12 + '@esbuild/linux-ia32': 0.19.12 + '@esbuild/linux-loong64': 0.19.12 + '@esbuild/linux-mips64el': 0.19.12 + '@esbuild/linux-ppc64': 0.19.12 + '@esbuild/linux-riscv64': 0.19.12 + '@esbuild/linux-s390x': 0.19.12 + '@esbuild/linux-x64': 0.19.12 + '@esbuild/netbsd-x64': 0.19.12 + '@esbuild/openbsd-x64': 0.19.12 + '@esbuild/sunos-x64': 0.19.12 + '@esbuild/win32-arm64': 0.19.12 + '@esbuild/win32-ia32': 0.19.12 + '@esbuild/win32-x64': 0.19.12 + dev: true + + /esbuild@0.25.0: + resolution: {integrity: sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==} + engines: {node: '>=18'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.0 + '@esbuild/android-arm': 0.25.0 + '@esbuild/android-arm64': 0.25.0 + '@esbuild/android-x64': 0.25.0 + '@esbuild/darwin-arm64': 0.25.0 + '@esbuild/darwin-x64': 0.25.0 + '@esbuild/freebsd-arm64': 0.25.0 + '@esbuild/freebsd-x64': 0.25.0 + '@esbuild/linux-arm': 0.25.0 + '@esbuild/linux-arm64': 0.25.0 + '@esbuild/linux-ia32': 0.25.0 + '@esbuild/linux-loong64': 0.25.0 + '@esbuild/linux-mips64el': 0.25.0 + '@esbuild/linux-ppc64': 0.25.0 + '@esbuild/linux-riscv64': 0.25.0 + '@esbuild/linux-s390x': 0.25.0 + '@esbuild/linux-x64': 0.25.0 + '@esbuild/netbsd-arm64': 0.25.0 + '@esbuild/netbsd-x64': 0.25.0 + '@esbuild/openbsd-arm64': 0.25.0 + '@esbuild/openbsd-x64': 0.25.0 + '@esbuild/sunos-x64': 0.25.0 + '@esbuild/win32-arm64': 0.25.0 + '@esbuild/win32-ia32': 0.25.0 + '@esbuild/win32-x64': 0.25.0 + + /escalade@3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + dev: true + + /escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + dev: true + + /escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + dev: true + + /escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + dev: true + + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true + + /escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + dev: true + + /escodegen@2.0.0: + resolution: {integrity: sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==} + engines: {node: '>=6.0'} + hasBin: true dependencies: - color-name: 1.1.3 + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionator: 0.8.3 + optionalDependencies: + source-map: 0.6.1 dev: true - /color-convert/2.0.1: - resolution: - { - integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, - } - engines: { node: '>=7.0.0' } + /eslint-compat-utils@0.5.1(eslint@8.47.0): + resolution: {integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==} + engines: {node: '>=12'} + peerDependencies: + eslint: '>=6.0.0' dependencies: - color-name: 1.1.4 - dev: true - - /color-name/1.1.3: - resolution: - { - integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==, - } - dev: true - - /color-name/1.1.4: - resolution: - { - integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, - } + eslint: 8.47.0 + semver: 7.6.3 dev: true - /color-support/1.1.3: - resolution: - { - integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==, - } + /eslint-config-prettier@9.0.0(eslint@8.47.0): + resolution: {integrity: sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==} hasBin: true - dev: true - - /colord/2.9.2: - resolution: - { - integrity: sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==, - } - dev: true - - /colorette/2.0.19: - resolution: - { - integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==, - } - dev: true - - /combined-stream/1.0.8: - resolution: - { - integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==, - } - engines: { node: '>= 0.8' } + peerDependencies: + eslint: '>=7.0.0' dependencies: - delayed-stream: 1.0.0 - dev: true - - /commander/2.20.3: - resolution: - { - integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==, - } - dev: true - - /commander/5.1.0: - resolution: - { - integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==, - } - engines: { node: '>= 6' } + eslint: 8.47.0 dev: true - /commander/7.2.0: - resolution: - { - integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==, - } - engines: { node: '>= 10' } - dev: true - - /commander/9.4.0: - resolution: - { - integrity: sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==, - } - engines: { node: ^12.20.0 || >=14 } - dev: true - - /common-tags/1.8.2: - resolution: - { - integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==, - } - engines: { node: '>=4.0.0' } - dev: true - - /commondir/1.0.1: - resolution: - { - integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==, - } + /eslint-import-resolver-node@0.3.7: + resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==} + dependencies: + debug: 3.2.7 + is-core-module: 2.13.0 + resolve: 1.22.1 + transitivePeerDependencies: + - supports-color dev: true - /concat-map/0.0.1: - resolution: - { - integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, - } + /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.6.0)(eslint-import-resolver-node@0.3.7)(eslint@8.47.0): + resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 6.6.0(eslint@8.47.0)(typescript@5.2.2) + debug: 3.2.7 + eslint: 8.47.0 + eslint-import-resolver-node: 0.3.7 + transitivePeerDependencies: + - supports-color dev: true - /concat-stream/1.6.2: - resolution: - { - integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==, - } - engines: { '0': node >= 0.8 } + /eslint-plugin-import@2.28.1(@typescript-eslint/parser@6.6.0)(eslint@8.47.0): + resolution: {integrity: sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true dependencies: - buffer-from: 1.1.2 - inherits: 2.0.4 - readable-stream: 2.3.7 - typedarray: 0.0.6 + '@typescript-eslint/parser': 6.6.0(eslint@8.47.0)(typescript@5.2.2) + array-includes: 3.1.6 + array.prototype.findlastindex: 1.2.2 + array.prototype.flat: 1.3.1 + array.prototype.flatmap: 1.3.1 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 8.47.0 + eslint-import-resolver-node: 0.3.7 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.6.0)(eslint-import-resolver-node@0.3.7)(eslint@8.47.0) + has: 1.0.3 + is-core-module: 2.13.0 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.6 + object.groupby: 1.0.0 + object.values: 1.1.6 + semver: 6.3.1 + tsconfig-paths: 3.14.2 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color dev: true - /concurrently/7.2.0: - resolution: - { - integrity: sha512-4KIVY5HopDRhN3ndAgfFOLsMk1PZUPgghlgTMZ5Pb5aTrqYg86RcZaIZC2Cz+qpZ9DsX36WHGjvWnXPqdnblhw==, - } - engines: { node: ^12.20.0 || ^14.13.0 || >=16.0.0 } - hasBin: true + /eslint-plugin-playwright@0.15.3(eslint@8.47.0): + resolution: {integrity: sha512-LQMW5y0DLK5Fnpya7JR1oAYL2/7Y9wDiYw6VZqlKqcRGSgjbVKNqxraphk7ra1U3Bb5EK444xMgUlQPbMg2M1g==} + peerDependencies: + eslint: '>=7' + eslint-plugin-jest: '>=25' + peerDependenciesMeta: + eslint-plugin-jest: + optional: true dependencies: - chalk: 4.1.2 - date-fns: 2.28.0 - lodash: 4.17.21 - rxjs: 6.6.7 - shell-quote: 1.7.3 - spawn-command: 0.0.2-1 - supports-color: 8.1.1 - tree-kill: 1.2.2 - yargs: 17.5.1 + eslint: 8.47.0 dev: true - /connect/3.7.0: - resolution: - { - integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==, - } - engines: { node: '>= 0.10.0' } + /eslint-plugin-storybook@0.8.0(eslint@8.47.0)(typescript@5.2.2): + resolution: {integrity: sha512-CZeVO5EzmPY7qghO2t64oaFM+8FTaD4uzOEjHKp516exyTKo+skKAL9GI3QALS2BXhyALJjNtwbmr1XinGE8bA==} + engines: {node: '>= 18'} + peerDependencies: + eslint: '>=6' dependencies: - debug: 2.6.9 - finalhandler: 1.1.2 - parseurl: 1.3.3 - utils-merge: 1.0.1 + '@storybook/csf': 0.0.1 + '@typescript-eslint/utils': 5.62.0(eslint@8.47.0)(typescript@5.2.2) + eslint: 8.47.0 + requireindex: 1.2.0 + ts-dedent: 2.2.0 transitivePeerDependencies: - supports-color + - typescript dev: true - /console-control-strings/1.1.0: - resolution: - { - integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==, - } + /eslint-plugin-svelte@2.46.1(eslint@8.47.0)(svelte@5.25.5): + resolution: {integrity: sha512-7xYr2o4NID/f9OEYMqxsEQsCsj4KaMy4q5sANaKkAb6/QeCjYFxRmDm2S3YC3A3pl1kyPZ/syOx/i7LcWYSbIw==} + engines: {node: ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0-0 || ^9.0.0-0 + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + svelte: + optional: true + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0) + '@jridgewell/sourcemap-codec': 1.5.0 + eslint: 8.47.0 + eslint-compat-utils: 0.5.1(eslint@8.47.0) + esutils: 2.0.3 + known-css-properties: 0.35.0 + postcss: 8.5.1 + postcss-load-config: 3.1.4(postcss@8.5.1) + postcss-safe-parser: 6.0.0(postcss@8.5.1) + postcss-selector-parser: 6.1.2 + semver: 7.6.3 + svelte: 5.25.5 + svelte-eslint-parser: 0.43.0(svelte@5.25.5) + transitivePeerDependencies: + - ts-node dev: true - /constant-case/3.0.4: - resolution: - { - integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==, - } + /eslint-plugin-vitest@0.2.8(eslint@8.47.0)(typescript@5.2.2)(vite@6.2.7)(vitest@3.1.1): + resolution: {integrity: sha512-q8s4tStyKtn3gXf+8nf1ZYTHhoCXKdnozZzp6u8b4ni5v68Y4vxhNh4Z8njUfNjEY8HoPBB77MazHMR23IPb+g==} + engines: {node: 14.x || >= 16} + peerDependencies: + eslint: '>=8.0.0' + vite: '*' + vitest: '*' + peerDependenciesMeta: + vite: + optional: true dependencies: - no-case: 3.0.4 - tslib: 2.4.0 - upper-case: 2.0.2 + '@typescript-eslint/utils': 6.4.1(eslint@8.47.0)(typescript@5.2.2) + eslint: 8.47.0 + vite: 6.2.7(@types/node@18.15.3) + vitest: 3.1.1(@types/node@18.15.3)(@vitest/ui@3.1.1)(jsdom@20.0.3) + transitivePeerDependencies: + - supports-color + - typescript dev: true - /convert-source-map/1.8.0: - resolution: - { - integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==, - } + /eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} dependencies: - safe-buffer: 5.1.2 + esrecurse: 4.3.0 + estraverse: 4.3.0 dev: true - /core-util-is/1.0.2: - resolution: - { - integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==, - } + /eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 dev: true - /cosmiconfig/7.0.1: - resolution: - { - integrity: sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==, - } - engines: { node: '>=10' } + /eslint-utils@3.0.0(eslint@8.4.1): + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' dependencies: - '@types/parse-json': 4.0.0 - import-fresh: 3.3.0 - parse-json: 5.2.0 - path-type: 4.0.0 - yaml: 1.10.2 + eslint: 8.4.1 + eslint-visitor-keys: 2.1.0 dev: true - /create-require/1.1.1: - resolution: - { - integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==, - } + /eslint-visitor-keys@2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} dev: true - /crelt/1.0.5: - resolution: - { - integrity: sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==, - } + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /cross-env/7.0.3: - resolution: - { - integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==, - } - engines: { node: '>=10.14', npm: '>=6', yarn: '>=1' } + /eslint@8.4.1: + resolution: {integrity: sha512-TxU/p7LB1KxQ6+7aztTnO7K0i+h0tDi81YRY9VzB6Id71kNz+fFYnf5HD5UOQmxkzcoa0TlVZf9dpMtUv0GpWg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true dependencies: - cross-spawn: 7.0.3 + '@eslint/eslintrc': 1.4.1 + '@humanwhocodes/config-array': 0.9.5 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + doctrine: 3.0.0 + enquirer: 2.4.1 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-utils: 3.0.0(eslint@8.4.1) + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + functional-red-black-tree: 1.0.1 + glob-parent: 6.0.2 + globals: 13.21.0 + ignore: 4.0.6 + import-fresh: 3.3.0 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.3 + progress: 2.0.3 + regexpp: 3.2.0 + semver: 7.6.3 + strip-ansi: 6.0.1 + strip-json-comments: 3.1.1 + text-table: 0.2.0 + v8-compile-cache: 2.4.0 + transitivePeerDependencies: + - supports-color dev: true - /cross-spawn/7.0.3: - resolution: - { - integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==, - } - engines: { node: '>= 8' } + /eslint@8.47.0: + resolution: {integrity: sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0) + '@eslint-community/regexpp': 4.7.0 + '@eslint/eslintrc': 2.1.2 + '@eslint/js': 8.47.0 + '@humanwhocodes/config-array': 0.11.10 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.19.0 + graphemer: 1.4.0 + ignore: 5.2.4 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.3 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color dev: true - /css-declaration-sorter/6.2.2_postcss@8.4.14: - resolution: - { - integrity: sha512-Ufadglr88ZLsrvS11gjeu/40Lw74D9Am/Jpr3LlYm5Q4ZP5KdlUhG+6u2EjyXeZcxmZ2h1ebCKngDjolpeLHpg==, - } - engines: { node: ^10 || ^12 || >=14 } - peerDependencies: - postcss: ^8.0.9 - dependencies: - postcss: 8.4.14 - dev: true + /esm-env@1.0.0: + resolution: {integrity: sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==} + dev: false - /css-functions-list/3.0.1: - resolution: - { - integrity: sha512-PriDuifDt4u4rkDgnqRCLnjfMatufLmWNfQnGCq34xZwpY3oabwhB9SqRBmuvWUgndbemCFlKqg+nO7C2q0SBw==, - } - engines: { node: '>=12.22' } - dev: true + /esm-env@1.2.2: + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} - /css-select/4.3.0: - resolution: - { - integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==, - } + /esno@0.16.3: + resolution: {integrity: sha512-6slSBEV1lMKcX13DBifvnDFpNno5WXhw4j/ff7RI0y51BZiDqEe5dNhhjhIQ3iCOQuzsm2MbVzmwqbN78BBhPg==} + hasBin: true dependencies: - boolbase: 1.0.0 - css-what: 6.1.0 - domhandler: 4.3.1 - domutils: 2.8.0 - nth-check: 2.0.1 + tsx: 3.12.3 dev: true - /css-tree/1.1.3: - resolution: - { - integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==, - } - engines: { node: '>=8.0.0' } + /espree@9.2.0: + resolution: {integrity: sha512-oP3utRkynpZWF/F2x/HZJ+AGtnIclaR7z1pYPxy7NYM2fSO6LgK/Rkny8anRSPK/VwEA1eqm2squui0T7ZMOBg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - mdn-data: 2.0.14 - source-map: 0.6.1 + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 3.4.3 dev: true - /css-what/6.1.0: - resolution: - { - integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==, - } - engines: { node: '>= 6' } - dev: true - - /css.escape/1.5.1: - resolution: - { - integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==, - } + /espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 3.4.3 dev: true - /cssesc/3.0.0: - resolution: - { - integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==, - } - engines: { node: '>=4' } + /esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} hasBin: true dev: true - /cssnano-preset-default/5.2.8_postcss@8.4.14: - resolution: - { - integrity: sha512-6xQXUhTAPupvib3KC0Gl0d1jIwGFcJyuWQiMcA6grprGdmIzt1cxG5z78VuZu6DRRS6qin6ETkQsH6ixxb/SQw==, - } - engines: { node: ^10 || ^12 || >=14.0 } - peerDependencies: - postcss: ^8.2.15 - dependencies: - css-declaration-sorter: 6.2.2_postcss@8.4.14 - cssnano-utils: 3.1.0_postcss@8.4.14 - postcss: 8.4.14 - postcss-calc: 8.2.4_postcss@8.4.14 - postcss-colormin: 5.3.0_postcss@8.4.14 - postcss-convert-values: 5.1.1_postcss@8.4.14 - postcss-discard-comments: 5.1.1_postcss@8.4.14 - postcss-discard-duplicates: 5.1.0_postcss@8.4.14 - postcss-discard-empty: 5.1.1_postcss@8.4.14 - postcss-discard-overridden: 5.1.0_postcss@8.4.14 - postcss-merge-longhand: 5.1.4_postcss@8.4.14 - postcss-merge-rules: 5.1.1_postcss@8.4.14 - postcss-minify-font-values: 5.1.0_postcss@8.4.14 - postcss-minify-gradients: 5.1.1_postcss@8.4.14 - postcss-minify-params: 5.1.3_postcss@8.4.14 - postcss-minify-selectors: 5.2.0_postcss@8.4.14 - postcss-normalize-charset: 5.1.0_postcss@8.4.14 - postcss-normalize-display-values: 5.1.0_postcss@8.4.14 - postcss-normalize-positions: 5.1.0_postcss@8.4.14 - postcss-normalize-repeat-style: 5.1.0_postcss@8.4.14 - postcss-normalize-string: 5.1.0_postcss@8.4.14 - postcss-normalize-timing-functions: 5.1.0_postcss@8.4.14 - postcss-normalize-unicode: 5.1.0_postcss@8.4.14 - postcss-normalize-url: 5.1.0_postcss@8.4.14 - postcss-normalize-whitespace: 5.1.1_postcss@8.4.14 - postcss-ordered-values: 5.1.1_postcss@8.4.14 - postcss-reduce-initial: 5.1.0_postcss@8.4.14 - postcss-reduce-transforms: 5.1.0_postcss@8.4.14 - postcss-svgo: 5.1.0_postcss@8.4.14 - postcss-unique-selectors: 5.1.1_postcss@8.4.14 - dev: true - - /cssnano-utils/3.1.0_postcss@8.4.14: - resolution: - { - integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==, - } - engines: { node: ^10 || ^12 || >=14.0 } - peerDependencies: - postcss: ^8.2.15 + /esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} dependencies: - postcss: 8.4.14 + estraverse: 5.3.0 dev: true - /cssnano/5.1.8_postcss@8.4.14: - resolution: - { - integrity: sha512-5lma/yQlK+6eOHSUqNAS11b4/fbiuasoxmCHoVYxSg6lQsyX7bGGIqiLi4o3Pe2CrUTrgcD2udW7JIgzC2806g==, - } - engines: { node: ^10 || ^12 || >=14.0 } - peerDependencies: - postcss: ^8.2.15 + /esrap@1.2.2: + resolution: {integrity: sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==} dependencies: - cssnano-preset-default: 5.2.8_postcss@8.4.14 - lilconfig: 2.0.5 - postcss: 8.4.14 - yaml: 1.10.2 + '@jridgewell/sourcemap-codec': 1.5.0 + '@types/estree': 1.0.7 dev: true - /csso/4.2.0: - resolution: - { - integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==, - } - engines: { node: '>=8.0.0' } + /esrap@1.4.5: + resolution: {integrity: sha512-CjNMjkBWWZeHn+VX+gS8YvFwJ5+NDhg8aWZBSFJPR8qQduDNjbJodA2WcwCm7uQa5Rjqj+nZvVmceg1RbHFB9g==} dependencies: - css-tree: 1.1.3 - dev: true + '@jridgewell/sourcemap-codec': 1.5.0 - /cssom/0.3.8: - resolution: - { - integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==, - } + /esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 dev: true - /cssom/0.5.0: - resolution: - { - integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==, - } + /estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} dev: true - /cssstyle/2.3.0: - resolution: - { - integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==, - } - engines: { node: '>=8' } - dependencies: - cssom: 0.3.8 + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} dev: true - /cypress/9.7.0: - resolution: - { - integrity: sha512-+1EE1nuuuwIt/N1KXRR2iWHU+OiIt7H28jJDyyI4tiUftId/DrXYEwoDa5+kH2pki1zxnA0r6HrUGHV5eLbF5Q==, - } - engines: { node: '>=12.0.0' } - hasBin: true - requiresBuild: true - dependencies: - '@cypress/request': 2.88.10 - '@cypress/xvfb': 1.2.4_supports-color@8.1.1 - '@types/node': 14.18.18 - '@types/sinonjs__fake-timers': 8.1.1 - '@types/sizzle': 2.3.3 - arch: 2.2.0 - blob-util: 2.0.2 - bluebird: 3.7.2 - buffer: 5.7.1 - cachedir: 2.3.0 - chalk: 4.1.2 - check-more-types: 2.24.0 - cli-cursor: 3.1.0 - cli-table3: 0.6.2 - commander: 5.1.0 - common-tags: 1.8.2 - dayjs: 1.11.2 - debug: 4.3.4_supports-color@8.1.1 - enquirer: 2.3.6 - eventemitter2: 6.4.5 - execa: 4.1.0 - executable: 4.1.1 - extract-zip: 2.0.1_supports-color@8.1.1 - figures: 3.2.0 - fs-extra: 9.1.0 - getos: 3.2.1 - is-ci: 3.0.1 - is-installed-globally: 0.4.0 - lazy-ass: 1.6.0 - listr2: 3.14.0_enquirer@2.3.6 - lodash: 4.17.21 - log-symbols: 4.1.0 - minimist: 1.2.6 - ospath: 1.2.2 - pretty-bytes: 5.6.0 - proxy-from-env: 1.0.0 - request-progress: 3.0.0 - semver: 7.3.7 - supports-color: 8.1.1 - tmp: 0.2.1 - untildify: 4.0.0 - yauzl: 2.10.0 + /estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} dev: true - /dashdash/1.14.1: - resolution: - { - integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==, - } - engines: { node: '>=0.10' } + /estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} dependencies: - assert-plus: 1.0.0 + '@types/estree': 1.0.7 dev: true - /data-urls/3.0.2: - resolution: - { - integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==, - } - engines: { node: '>=12' } - dependencies: - abab: 2.0.6 - whatwg-mimetype: 3.0.0 - whatwg-url: 11.0.0 + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} dev: true - /dataloader/1.4.0: - resolution: - { - integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==, - } + /etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} dev: true - /date-fns-tz/1.3.4_date-fns@2.28.0: - resolution: - { - integrity: sha512-O47vEyz85F2ax/ZdhMBJo187RivZGjH6V0cPjPzpm/yi6YffJg4upD/8ibezO11ezZwP3QYlBHh/t4JhRNx0Ow==, - } - peerDependencies: - date-fns: '>=2.0.0' + /event-stream@3.3.4: + resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} dependencies: - date-fns: 2.28.0 - dev: false - - /date-fns/2.28.0: - resolution: - { - integrity: sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==, - } - engines: { node: '>=0.11' } - - /dayjs/1.11.2: - resolution: - { - integrity: sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw==, - } + duplexer: 0.1.2 + from: 0.1.7 + map-stream: 0.1.0 + pause-stream: 0.0.11 + split: 0.3.3 + stream-combiner: 0.0.4 + through: 2.3.8 dev: true - /debug/2.6.9: - resolution: - { - integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==, - } - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.0.0 + /event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} dev: true - /debug/3.2.7_supports-color@8.1.1: - resolution: - { - integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==, - } - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - supports-color: 8.1.1 + /events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} dev: true - /debug/4.3.4: - resolution: - { - integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==, - } - engines: { node: '>=6.0' } - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + /execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} dependencies: - ms: 2.1.2 + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 dev: true - /debug/4.3.4_supports-color@8.1.1: - resolution: - { - integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==, - } - engines: { node: '>=6.0' } - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + /execa@6.1.0: + resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: - ms: 2.1.2 - supports-color: 8.1.1 + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 3.0.1 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.1.0 + onetime: 6.0.0 + signal-exit: 3.0.7 + strip-final-newline: 3.0.0 dev: true - /decamelize-keys/1.1.0: - resolution: - { - integrity: sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==, - } - engines: { node: '>=0.10.0' } - dependencies: - decamelize: 1.2.0 - map-obj: 1.0.1 + /exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} dev: true - /decamelize/1.2.0: - resolution: - { - integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==, - } - engines: { node: '>=0.10.0' } + /expand-tilde@1.2.2: + resolution: {integrity: sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==} + engines: {node: '>=0.10.0'} + dependencies: + os-homedir: 1.0.2 dev: true - /decimal.js/10.3.1: - resolution: - { - integrity: sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==, - } + /expect-playwright@0.8.0: + resolution: {integrity: sha512-+kn8561vHAY+dt+0gMqqj1oY+g5xWrsuGMk4QGxotT2WS545nVqqjs37z6hrYfIuucwqthzwJfCJUEYqixyljg==} dev: true - /dedent-js/1.0.1: - resolution: - { - integrity: sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==, - } + /expect-type@1.2.0: + resolution: {integrity: sha512-80F22aiJ3GLyVnS/B3HzgR6RelZVumzj9jkL0Rhz4h0xYbNW9PjlQz5h3J/SShErbXBc295vseR4/MIbVmUbeA==} + engines: {node: '>=12.0.0'} dev: true - /deep-eql/3.0.1: - resolution: - { - integrity: sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==, - } - engines: { node: '>=0.12' } + /expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - type-detect: 4.0.8 + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 dev: true - /deep-is/0.1.4: - resolution: - { - integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, - } + /express@4.20.0: + resolution: {integrity: sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==} + engines: {node: '>= 0.10.0'} + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.3 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.6.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.2.0 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.10 + proxy-addr: 2.0.7 + qs: 6.11.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.0 + serve-static: 1.16.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color dev: true - /deepmerge/4.2.2: - resolution: - { - integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==, - } - engines: { node: '>=0.10.0' } + /extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - /define-properties/1.1.4: - resolution: - { - integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==, - } - engines: { node: '>= 0.4' } + /fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + requiresBuild: true dependencies: - has-property-descriptors: 1.0.0 - object-keys: 1.1.1 + pure-rand: 6.1.0 dev: false + optional: true - /defined/1.0.0: - resolution: - { - integrity: sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==, - } - dev: true - - /defu/6.1.0: - resolution: - { - integrity: sha512-pOFYRTIhoKujrmbTRhcW5lYQLBXw/dlTwfI8IguF1QCDJOcJzNH1w+YFjxqy6BAuJrClTy6MUE8q+oKJ2FLsIw==, - } + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true - /delayed-stream/1.0.0: - resolution: - { - integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==, - } - engines: { node: '>=0.4.0' } + /fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} dev: true - /delegates/1.0.0: - resolution: - { - integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==, - } + /fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 dev: true - /dependency-graph/0.11.0: - resolution: - { - integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==, - } - engines: { node: '>= 0.6.0' } + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true - /detect-indent/6.1.0: - resolution: - { - integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==, - } - engines: { node: '>=8' } + /fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true - /detect-libc/2.0.1: - resolution: - { - integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==, - } - engines: { node: '>=8' } + /fastest-levenshtein@1.0.16: + resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} + engines: {node: '>= 4.9.1'} dev: true - /detective/5.2.1: - resolution: - { - integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==, - } - engines: { node: '>=0.8.0' } - hasBin: true + /fastq@1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} dependencies: - acorn-node: 1.8.2 - defined: 1.0.0 - minimist: 1.2.6 + reusify: 1.0.4 dev: true - /diacritics/1.3.0: - resolution: - { - integrity: sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA==, - } + /fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + dependencies: + bser: 2.1.1 dev: true - /didyoumean/1.2.2: - resolution: - { - integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==, - } + /fdir@6.4.3(picomatch@4.0.2): + resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + dependencies: + picomatch: 4.0.2 dev: true - /diff-sequences/27.5.1: - resolution: - { - integrity: sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==, - } - engines: { node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0 } + /fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.2.1 dev: true - /diff/4.0.2: - resolution: - { - integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==, - } - engines: { node: '>=0.3.1' } + /fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} dev: true - /dir-glob/3.0.1: - resolution: - { - integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==, - } - engines: { node: '>=8' } + /file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} dependencies: - path-type: 4.0.0 + flat-cache: 3.0.4 dev: true - /dlv/1.1.3: - resolution: - { - integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==, - } + /file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} dev: true - /doctrine/3.0.0: - resolution: - { - integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==, - } - engines: { node: '>=6.0.0' } - dependencies: - esutils: 2.0.3 + /filesize@10.1.2: + resolution: {integrity: sha512-Dx770ai81ohflojxhU+oG+Z2QGvKdYxgEr9OSA8UVrqhwNHjfH9A8f5NKfg83fEH8ZFA5N5llJo5T3PIoZ4CRA==} + engines: {node: '>= 10.4.0'} dev: true - /dom-serializer/1.4.1: - resolution: - { - integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==, - } + /fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} dependencies: - domelementtype: 2.3.0 - domhandler: 4.3.1 - entities: 2.2.0 - - /domelementtype/2.3.0: - resolution: - { - integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==, - } - - /domexception/4.0.0: - resolution: - { - integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==, - } - engines: { node: '>=12' } - dependencies: - webidl-conversions: 7.0.0 + to-regex-range: 5.0.1 dev: true - /domhandler/4.3.1: - resolution: - { - integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==, - } - engines: { node: '>= 4' } + /finalhandler@1.2.0: + resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + engines: {node: '>= 0.8'} dependencies: - domelementtype: 2.3.0 - - /domutils/2.8.0: - resolution: - { - integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==, - } - dependencies: - dom-serializer: 1.4.1 - domelementtype: 2.3.0 - domhandler: 4.3.1 + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: true - /dot-case/3.0.4: - resolution: - { - integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==, - } + /find-cache-dir@3.3.2: + resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} + engines: {node: '>=8'} dependencies: - no-case: 3.0.4 - tslib: 2.4.0 + commondir: 1.0.1 + make-dir: 3.1.0 + pkg-dir: 4.2.0 dev: true - /eastasianwidth/0.2.0: - resolution: - { - integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==, - } + /find-file-up@0.1.3: + resolution: {integrity: sha512-mBxmNbVyjg1LQIIpgO8hN+ybWBgDQK8qjht+EbrTCGmmPV/sc7RF1i9stPTD6bpvXZywBdrwRYxhSdJv867L6A==} + engines: {node: '>=0.10.0'} + dependencies: + fs-exists-sync: 0.1.0 + resolve-dir: 0.1.1 dev: true - /ecc-jsbn/0.1.2: - resolution: - { - integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==, - } + /find-pkg@0.1.2: + resolution: {integrity: sha512-0rnQWcFwZr7eO0513HahrWafsc3CTFioEB7DRiEYCUM/70QXSY8f3mCST17HXLcPvEhzH/Ty/Bxd72ZZsr/yvw==} + engines: {node: '>=0.10.0'} dependencies: - jsbn: 0.1.1 - safer-buffer: 2.1.2 + find-file-up: 0.1.3 dev: true - /ee-first/1.1.1: - resolution: - { - integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==, - } + /find-process@1.4.7: + resolution: {integrity: sha512-/U4CYp1214Xrp3u3Fqr9yNynUrr5Le4y0SsJh2lMDDSbpwYSz3M2SMWQC+wqcx79cN8PQtHQIL8KnuY9M66fdg==} + hasBin: true + dependencies: + chalk: 4.1.2 + commander: 5.1.0 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color dev: true - /electron-to-chromium/1.4.137: - resolution: - { - integrity: sha512-0Rcpald12O11BUogJagX3HsCN3FE83DSqWjgXoHo5a72KUKMSfI39XBgJpgNNxS9fuGzytaFjE06kZkiVFy2qA==, - } + /find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 dev: true - /emoji-regex/8.0.0: - resolution: - { - integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, - } + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 dev: true - /emoji-regex/9.2.2: - resolution: - { - integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, - } + /flat-cache@3.0.4: + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: 3.3.3 + rimraf: 3.0.2 dev: true - /emojis-list/3.0.0: - resolution: - { - integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==, - } - engines: { node: '>= 4' } + /flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} dev: true - /encodeurl/1.0.2: - resolution: - { - integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==, - } - engines: { node: '>= 0.8' } + /follow-redirects@1.15.6: + resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true dev: true - /end-of-stream/1.4.4: - resolution: - { - integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==, - } + /for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: - once: 1.4.0 + is-callable: 1.2.7 dev: true - /enhanced-resolve/5.9.3: - resolution: - { - integrity: sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==, - } - engines: { node: '>=10.13.0' } + /foreground-child@2.0.0: + resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} + engines: {node: '>=8.0.0'} dependencies: - graceful-fs: 4.2.10 - tapable: 2.2.1 + cross-spawn: 7.0.6 + signal-exit: 3.0.7 dev: true - /enquirer/2.3.6: - resolution: - { - integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==, - } - engines: { node: '>=8.6' } + /foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} dependencies: - ansi-colors: 4.1.3 - dev: true - - /entities/2.1.0: - resolution: - { - integrity: sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==, - } + cross-spawn: 7.0.6 + signal-exit: 4.1.0 dev: true - /entities/2.2.0: - resolution: - { - integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==, - } - - /entities/4.3.0: - resolution: - { - integrity: sha512-/iP1rZrSEJ0DTlPiX+jbzlA3eVkY/e8L8SozroF395fIqE3TYF/Nz7YOMAawta+vLmyJ/hkGNNPcSbMADCCXbg==, - } - engines: { node: '>=0.12' } - dev: true - - /error-ex/1.3.2: - resolution: - { - integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==, - } + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} dependencies: - is-arrayish: 0.2.1 + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 dev: true - /es-abstract/1.20.1: - resolution: - { - integrity: sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==, - } - engines: { node: '>= 0.4' } + /formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} dependencies: - call-bind: 1.0.2 - es-to-primitive: 1.2.1 - function-bind: 1.1.1 - function.prototype.name: 1.1.5 - get-intrinsic: 1.1.1 - get-symbol-description: 1.0.0 - has: 1.0.3 - has-property-descriptors: 1.0.0 - has-symbols: 1.0.3 - internal-slot: 1.0.3 - is-callable: 1.2.4 - is-negative-zero: 2.0.2 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 - is-string: 1.0.7 - is-weakref: 1.0.2 - object-inspect: 1.12.2 - object-keys: 1.1.1 - object.assign: 4.1.2 - regexp.prototype.flags: 1.4.3 - string.prototype.trimend: 1.0.5 - string.prototype.trimstart: 1.0.5 - unbox-primitive: 1.0.2 - dev: false - - /es-module-lexer/0.9.3: - resolution: - { - integrity: sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==, - } + fetch-blob: 3.2.0 dev: true - /es-to-primitive/1.2.1: - resolution: - { - integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==, - } - engines: { node: '>= 0.4' } - dependencies: - is-callable: 1.2.4 - is-date-object: 1.0.5 - is-symbol: 1.0.4 - dev: false - - /es6-promise/3.3.1: - resolution: - { - integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==, - } + /forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} dev: true - /esbuild-android-64/0.14.54: - resolution: - { - integrity: sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==, - } - engines: { node: '>=12' } - cpu: [x64] - os: [android] - requiresBuild: true + /fraction.js@4.2.0: + resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} dev: true - optional: true - /esbuild-android-64/0.15.7: - resolution: - { - integrity: sha512-p7rCvdsldhxQr3YHxptf1Jcd86dlhvc3EQmQJaZzzuAxefO9PvcI0GLOa5nCWem1AJ8iMRu9w0r5TG8pHmbi9w==, - } - engines: { node: '>=12' } - cpu: [x64] - os: [android] - requiresBuild: true + /fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} dev: true - optional: true - /esbuild-android-arm64/0.13.15: - resolution: - { - integrity: sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg==, - } - cpu: [arm64] - os: [android] - requiresBuild: true + /from@0.1.7: + resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} dev: true - optional: true - /esbuild-android-arm64/0.14.54: - resolution: - { - integrity: sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==, - } - engines: { node: '>=12' } - cpu: [arm64] - os: [android] - requiresBuild: true + /fromentries@1.3.2: + resolution: {integrity: sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==} dev: true - optional: true - /esbuild-android-arm64/0.15.7: - resolution: - { - integrity: sha512-L775l9ynJT7rVqRM5vo+9w5g2ysbOCfsdLV4CWanTZ1k/9Jb3IYlQ06VCI1edhcosTYJRECQFJa3eAvkx72eyQ==, - } - engines: { node: '>=12' } - cpu: [arm64] - os: [android] - requiresBuild: true + /fs-exists-sync@0.1.0: + resolution: {integrity: sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==} + engines: {node: '>=0.10.0'} dev: true - optional: true - /esbuild-darwin-64/0.13.15: - resolution: - { - integrity: sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ==, - } - cpu: [x64] - os: [darwin] - requiresBuild: true + /fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.0 dev: true - optional: true - /esbuild-darwin-64/0.14.54: - resolution: - { - integrity: sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==, - } - engines: { node: '>=12' } - cpu: [x64] - os: [darwin] - requiresBuild: true + /fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 dev: true - optional: true - /esbuild-darwin-64/0.15.7: - resolution: - { - integrity: sha512-KGPt3r1c9ww009t2xLB6Vk0YyNOXh7hbjZ3EecHoVDxgtbUlYstMPDaReimKe6eOEfyY4hBEEeTvKwPsiH5WZg==, - } - engines: { node: '>=12' } - cpu: [x64] - os: [darwin] - requiresBuild: true + /fs-monkey@1.0.3: + resolution: {integrity: sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==} dev: true - optional: true - /esbuild-darwin-arm64/0.13.15: - resolution: - { - integrity: sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ==, - } - cpu: [arm64] - os: [darwin] - requiresBuild: true + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true - optional: true - /esbuild-darwin-arm64/0.14.54: - resolution: - { - integrity: sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==, - } - engines: { node: '>=12' } - cpu: [arm64] + /fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true dev: true optional: true - /esbuild-darwin-arm64/0.15.7: - resolution: - { - integrity: sha512-kBIHvtVqbSGajN88lYMnR3aIleH3ABZLLFLxwL2stiuIGAjGlQW741NxVTpUHQXUmPzxi6POqc9npkXa8AcSZQ==, - } - engines: { node: '>=12' } - cpu: [arm64] + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true - dev: true optional: true - /esbuild-freebsd-64/0.13.15: - resolution: - { - integrity: sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA==, - } - cpu: [x64] - os: [freebsd] - requiresBuild: true + /function-bind@1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} dev: true - optional: true - /esbuild-freebsd-64/0.14.54: - resolution: - { - integrity: sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==, - } - engines: { node: '>=12' } - cpu: [x64] - os: [freebsd] - requiresBuild: true + /function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} dev: true - optional: true - /esbuild-freebsd-64/0.15.7: - resolution: - { - integrity: sha512-hESZB91qDLV5MEwNxzMxPfbjAhOmtfsr9Wnuci7pY6TtEh4UDuevmGmkUIjX/b+e/k4tcNBMf7SRQ2mdNuK/HQ==, - } - engines: { node: '>=12' } - cpu: [x64] - os: [freebsd] - requiresBuild: true + /function.prototype.name@1.1.5: + resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.0 + es-abstract: 1.22.1 + functions-have-names: 1.2.3 dev: true - optional: true - /esbuild-freebsd-arm64/0.13.15: - resolution: - { - integrity: sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ==, - } - cpu: [arm64] - os: [freebsd] - requiresBuild: true + /functional-red-black-tree@1.0.1: + resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} dev: true - optional: true - /esbuild-freebsd-arm64/0.14.54: - resolution: - { - integrity: sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==, - } - engines: { node: '>=12' } - cpu: [arm64] - os: [freebsd] - requiresBuild: true + /functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} dev: true - optional: true - /esbuild-freebsd-arm64/0.15.7: - resolution: - { - integrity: sha512-dLFR0ChH5t+b3J8w0fVKGvtwSLWCv7GYT2Y2jFGulF1L5HftQLzVGN+6pi1SivuiVSmTh28FwUhi9PwQicXI6Q==, - } - engines: { node: '>=12' } - cpu: [arm64] - os: [freebsd] - requiresBuild: true + /gauge@3.0.2: + resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} + engines: {node: '>=10'} + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + object-assign: 4.1.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 dev: true - optional: true - /esbuild-linux-32/0.13.15: - resolution: - { - integrity: sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g==, - } - cpu: [ia32] - os: [linux] - requiresBuild: true + /gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} dev: true - optional: true - /esbuild-linux-32/0.14.54: - resolution: - { - integrity: sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==, - } - engines: { node: '>=12' } - cpu: [ia32] - os: [linux] - requiresBuild: true + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} dev: true - optional: true - /esbuild-linux-32/0.15.7: - resolution: - { - integrity: sha512-v3gT/LsONGUZcjbt2swrMjwxo32NJzk+7sAgtxhGx1+ZmOFaTRXBAi1PPfgpeo/J//Un2jIKm/I+qqeo4caJvg==, - } - engines: { node: '>=12' } - cpu: [ia32] - os: [linux] - requiresBuild: true + /get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + has-proto: 1.0.1 + has-symbols: 1.0.3 + hasown: 2.0.2 dev: true - optional: true - /esbuild-linux-64/0.13.15: - resolution: - { - integrity: sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA==, - } - cpu: [x64] - os: [linux] - requiresBuild: true + /get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} dev: true - optional: true - /esbuild-linux-64/0.14.54: - resolution: - { - integrity: sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==, - } - engines: { node: '>=12' } - cpu: [x64] - os: [linux] - requiresBuild: true + /get-stdin@9.0.0: + resolution: {integrity: sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==} + engines: {node: '>=12'} dev: true - optional: true - /esbuild-linux-64/0.15.7: - resolution: - { - integrity: sha512-LxXEfLAKwOVmm1yecpMmWERBshl+Kv5YJ/1KnyAr6HRHFW8cxOEsEfisD3sVl/RvHyW//lhYUVSuy9jGEfIRAQ==, - } - engines: { node: '>=12' } - cpu: [x64] - os: [linux] - requiresBuild: true + /get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} dev: true - optional: true - /esbuild-linux-arm/0.13.15: - resolution: - { - integrity: sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA==, - } - cpu: [arm] - os: [linux] - requiresBuild: true + /get-symbol-description@1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 dev: true - optional: true - /esbuild-linux-arm/0.14.54: - resolution: - { - integrity: sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==, - } - engines: { node: '>=12' } - cpu: [arm] - os: [linux] - requiresBuild: true + /get-tsconfig@4.4.0: + resolution: {integrity: sha512-0Gdjo/9+FzsYhXCEFueo2aY1z1tpXrxWZzP7k8ul9qt1U5o8rYJwTJYmaeHdrVosYIVYkOy2iwCJ9FdpocJhPQ==} dev: true - optional: true - /esbuild-linux-arm/0.15.7: - resolution: - { - integrity: sha512-JKgAHtMR5f75wJTeuNQbyznZZa+pjiUHV7sRZp42UNdyXC6TiUYMW/8z8yIBAr2Fpad8hM1royZKQisqPABPvQ==, - } - engines: { node: '>=12' } - cpu: [arm] - os: [linux] - requiresBuild: true + /github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} dev: true - optional: true - /esbuild-linux-arm64/0.13.15: - resolution: - { - integrity: sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA==, - } - cpu: [arm64] - os: [linux] - requiresBuild: true + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 dev: true - optional: true - /esbuild-linux-arm64/0.14.54: - resolution: - { - integrity: sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==, - } - engines: { node: '>=12' } - cpu: [arm64] - os: [linux] - requiresBuild: true + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 dev: true - optional: true - /esbuild-linux-arm64/0.15.7: - resolution: - { - integrity: sha512-P3cfhudpzWDkglutWgXcT2S7Ft7o2e3YDMrP1n0z2dlbUZghUkKCyaWw0zhp4KxEEzt/E7lmrtRu/pGWnwb9vw==, - } - engines: { node: '>=12' } - cpu: [arm64] - os: [linux] - requiresBuild: true + /glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} dev: true - optional: true - /esbuild-linux-mips64le/0.13.15: - resolution: - { - integrity: sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg==, - } - cpu: [mips64el] - os: [linux] - requiresBuild: true + /glob@10.3.10: + resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.3 + minipass: 7.0.2 + path-scurry: 1.10.1 dev: true - optional: true - /esbuild-linux-mips64le/0.14.54: - resolution: - { - integrity: sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==, - } - engines: { node: '>=12' } - cpu: [mips64el] - os: [linux] - requiresBuild: true + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 dev: true - optional: true - /esbuild-linux-mips64le/0.15.7: - resolution: - { - integrity: sha512-T7XKuxl0VpeFLCJXub6U+iybiqh0kM/bWOTb4qcPyDDwNVhLUiPcGdG2/0S7F93czUZOKP57YiLV8YQewgLHKw==, - } - engines: { node: '>=12' } - cpu: [mips64el] - os: [linux] - requiresBuild: true + /glob@9.3.5: + resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + fs.realpath: 1.0.0 + minimatch: 8.0.4 + minipass: 4.2.8 + path-scurry: 1.10.1 dev: true - optional: true - /esbuild-linux-ppc64le/0.13.15: - resolution: - { - integrity: sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ==, - } - cpu: [ppc64] - os: [linux] - requiresBuild: true + /global-modules@0.2.3: + resolution: {integrity: sha512-JeXuCbvYzYXcwE6acL9V2bAOeSIGl4dD+iwLY9iUx2VBJJ80R18HCn+JCwHM9Oegdfya3lEkGCdaRkSyc10hDA==} + engines: {node: '>=0.10.0'} + dependencies: + global-prefix: 0.1.5 + is-windows: 0.2.0 dev: true - optional: true - /esbuild-linux-ppc64le/0.14.54: - resolution: - { - integrity: sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==, - } - engines: { node: '>=12' } - cpu: [ppc64] - os: [linux] - requiresBuild: true + /global-modules@2.0.0: + resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} + engines: {node: '>=6'} + dependencies: + global-prefix: 3.0.0 dev: true - optional: true - /esbuild-linux-ppc64le/0.15.7: - resolution: - { - integrity: sha512-6mGuC19WpFN7NYbecMIJjeQgvDb5aMuvyk0PDYBJrqAEMkTwg3Z98kEKuCm6THHRnrgsdr7bp4SruSAxEM4eJw==, - } - engines: { node: '>=12' } - cpu: [ppc64] - os: [linux] - requiresBuild: true + /global-prefix@0.1.5: + resolution: {integrity: sha512-gOPiyxcD9dJGCEArAhF4Hd0BAqvAe/JzERP7tYumE4yIkmIedPUVXcJFWbV3/p/ovIIvKjkrTk+f1UVkq7vvbw==} + engines: {node: '>=0.10.0'} + dependencies: + homedir-polyfill: 1.0.3 + ini: 1.3.8 + is-windows: 0.2.0 + which: 1.3.1 dev: true - optional: true - /esbuild-linux-riscv64/0.14.54: - resolution: - { - integrity: sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==, - } - engines: { node: '>=12' } - cpu: [riscv64] - os: [linux] - requiresBuild: true + /global-prefix@3.0.0: + resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} + engines: {node: '>=6'} + dependencies: + ini: 1.3.8 + kind-of: 6.0.3 + which: 1.3.1 dev: true - optional: true - /esbuild-linux-riscv64/0.15.7: - resolution: - { - integrity: sha512-uUJsezbswAYo/X7OU/P+PuL/EI9WzxsEQXDekfwpQ23uGiooxqoLFAPmXPcRAt941vjlY9jtITEEikWMBr+F/g==, - } - engines: { node: '>=12' } - cpu: [riscv64] - os: [linux] - requiresBuild: true + /globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} dev: true - optional: true - /esbuild-linux-s390x/0.14.54: - resolution: - { - integrity: sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==, - } - engines: { node: '>=12' } - cpu: [s390x] - os: [linux] - requiresBuild: true + /globals@13.19.0: + resolution: {integrity: sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 dev: true - optional: true - /esbuild-linux-s390x/0.15.7: - resolution: - { - integrity: sha512-+tO+xOyTNMc34rXlSxK7aCwJgvQyffqEM5MMdNDEeMU3ss0S6wKvbBOQfgd5jRPblfwJ6b+bKiz0g5nABpY0QQ==, - } - engines: { node: '>=12' } - cpu: [s390x] - os: [linux] - requiresBuild: true + /globals@13.21.0: + resolution: {integrity: sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 dev: true - optional: true - /esbuild-netbsd-64/0.13.15: - resolution: - { - integrity: sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w==, - } - cpu: [x64] - os: [netbsd] - requiresBuild: true + /globalthis@1.0.3: + resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + engines: {node: '>= 0.4'} + dependencies: + define-properties: 1.2.0 dev: true - optional: true - /esbuild-netbsd-64/0.14.54: - resolution: - { - integrity: sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==, - } - engines: { node: '>=12' } - cpu: [x64] - os: [netbsd] - requiresBuild: true + /globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.1 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 3.0.0 dev: true - optional: true - /esbuild-netbsd-64/0.15.7: - resolution: - { - integrity: sha512-yVc4Wz+Pu3cP5hzm5kIygNPrjar/v5WCSoRmIjCPWfBVJkZNb5brEGKUlf+0Y759D48BCWa0WHrWXaNy0DULTQ==, - } - engines: { node: '>=12' } - cpu: [x64] - os: [netbsd] - requiresBuild: true + /globby@12.2.0: + resolution: {integrity: sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + array-union: 3.0.1 + dir-glob: 3.0.1 + fast-glob: 3.3.1 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 4.0.0 dev: true - optional: true - /esbuild-openbsd-64/0.13.15: - resolution: - { - integrity: sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g==, - } - cpu: [x64] - os: [openbsd] - requiresBuild: true + /globby@13.1.3: + resolution: {integrity: sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + dir-glob: 3.0.1 + fast-glob: 3.3.1 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 4.0.0 dev: true - optional: true - /esbuild-openbsd-64/0.14.54: - resolution: - { - integrity: sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==, - } - engines: { node: '>=12' } - cpu: [x64] - os: [openbsd] - requiresBuild: true + /globjoin@0.1.4: + resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} dev: true - optional: true - /esbuild-openbsd-64/0.15.7: - resolution: - { - integrity: sha512-GsimbwC4FSR4lN3wf8XmTQ+r8/0YSQo21rWDL0XFFhLHKlzEA4SsT1Tl8bPYu00IU6UWSJ+b3fG/8SB69rcuEQ==, - } - engines: { node: '>=12' } - cpu: [x64] - os: [openbsd] - requiresBuild: true + /google-protobuf@3.21.2: + resolution: {integrity: sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==} dev: true - optional: true - /esbuild-sunos-64/0.13.15: - resolution: - { - integrity: sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw==, - } - cpu: [x64] - os: [sunos] - requiresBuild: true + /gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + dependencies: + get-intrinsic: 1.2.4 dev: true - optional: true - /esbuild-sunos-64/0.14.54: - resolution: - { - integrity: sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==, - } - engines: { node: '>=12' } - cpu: [x64] - os: [sunos] - requiresBuild: true + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} dev: true - optional: true - /esbuild-sunos-64/0.15.7: - resolution: - { - integrity: sha512-8CDI1aL/ts0mDGbWzjEOGKXnU7p3rDzggHSBtVryQzkSOsjCHRVe0iFYUuhczlxU1R3LN/E7HgUO4NXzGGP/Ag==, - } - engines: { node: '>=12' } - cpu: [x64] - os: [sunos] - requiresBuild: true + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true - optional: true - /esbuild-windows-32/0.13.15: - resolution: - { - integrity: sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw==, - } - cpu: [ia32] - os: [win32] - requiresBuild: true + /hard-rejection@2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} dev: true - optional: true - /esbuild-windows-32/0.14.54: - resolution: - { - integrity: sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==, - } - engines: { node: '>=12' } - cpu: [ia32] - os: [win32] - requiresBuild: true + /has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} dev: true - optional: true - /esbuild-windows-32/0.15.7: - resolution: - { - integrity: sha512-cOnKXUEPS8EGCzRSFa1x6NQjGhGsFlVgjhqGEbLTPsA7x4RRYiy2RKoArNUU4iR2vHmzqS5Gr84MEumO/wxYKA==, - } - engines: { node: '>=12' } - cpu: [ia32] - os: [win32] - requiresBuild: true + /has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} dev: true - optional: true - /esbuild-windows-64/0.13.15: - resolution: - { - integrity: sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ==, - } - cpu: [x64] - os: [win32] - requiresBuild: true + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} dev: true - optional: true - /esbuild-windows-64/0.14.54: - resolution: - { - integrity: sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==, - } - engines: { node: '>=12' } - cpu: [x64] - os: [win32] - requiresBuild: true + /has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + dependencies: + es-define-property: 1.0.0 dev: true - optional: true - /esbuild-windows-64/0.15.7: - resolution: - { - integrity: sha512-7MI08Ec2sTIDv+zH6StNBKO+2hGUYIT42GmFyW6MBBWWtJhTcQLinKS6ldIN1d52MXIbiJ6nXyCJ+LpL4jBm3Q==, - } - engines: { node: '>=12' } - cpu: [x64] - os: [win32] - requiresBuild: true + /has-proto@1.0.1: + resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + engines: {node: '>= 0.4'} dev: true - optional: true - /esbuild-windows-arm64/0.13.15: - resolution: - { - integrity: sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA==, - } - cpu: [arm64] - os: [win32] - requiresBuild: true + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} dev: true - optional: true - /esbuild-windows-arm64/0.14.54: - resolution: - { - integrity: sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==, - } - engines: { node: '>=12' } - cpu: [arm64] - os: [win32] - requiresBuild: true + /has-tostringtag@1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 dev: true - optional: true - /esbuild-windows-arm64/0.15.7: - resolution: - { - integrity: sha512-R06nmqBlWjKHddhRJYlqDd3Fabx9LFdKcjoOy08YLimwmsswlFBJV4rXzZCxz/b7ZJXvrZgj8DDv1ewE9+StMw==, - } - engines: { node: '>=12' } - cpu: [arm64] - os: [win32] - requiresBuild: true + /has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} dev: true - optional: true - /esbuild/0.13.15: - resolution: - { - integrity: sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw==, - } - hasBin: true - requiresBuild: true - optionalDependencies: - esbuild-android-arm64: 0.13.15 - esbuild-darwin-64: 0.13.15 - esbuild-darwin-arm64: 0.13.15 - esbuild-freebsd-64: 0.13.15 - esbuild-freebsd-arm64: 0.13.15 - esbuild-linux-32: 0.13.15 - esbuild-linux-64: 0.13.15 - esbuild-linux-arm: 0.13.15 - esbuild-linux-arm64: 0.13.15 - esbuild-linux-mips64le: 0.13.15 - esbuild-linux-ppc64le: 0.13.15 - esbuild-netbsd-64: 0.13.15 - esbuild-openbsd-64: 0.13.15 - esbuild-sunos-64: 0.13.15 - esbuild-windows-32: 0.13.15 - esbuild-windows-64: 0.13.15 - esbuild-windows-arm64: 0.13.15 - dev: true - - /esbuild/0.14.54: - resolution: - { - integrity: sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==, - } - engines: { node: '>=12' } - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/linux-loong64': 0.14.54 - esbuild-android-64: 0.14.54 - esbuild-android-arm64: 0.14.54 - esbuild-darwin-64: 0.14.54 - esbuild-darwin-arm64: 0.14.54 - esbuild-freebsd-64: 0.14.54 - esbuild-freebsd-arm64: 0.14.54 - esbuild-linux-32: 0.14.54 - esbuild-linux-64: 0.14.54 - esbuild-linux-arm: 0.14.54 - esbuild-linux-arm64: 0.14.54 - esbuild-linux-mips64le: 0.14.54 - esbuild-linux-ppc64le: 0.14.54 - esbuild-linux-riscv64: 0.14.54 - esbuild-linux-s390x: 0.14.54 - esbuild-netbsd-64: 0.14.54 - esbuild-openbsd-64: 0.14.54 - esbuild-sunos-64: 0.14.54 - esbuild-windows-32: 0.14.54 - esbuild-windows-64: 0.14.54 - esbuild-windows-arm64: 0.14.54 - dev: true - - /esbuild/0.15.7: - resolution: - { - integrity: sha512-7V8tzllIbAQV1M4QoE52ImKu8hT/NLGlGXkiDsbEU5PS6K8Mn09ZnYoS+dcmHxOS9CRsV4IRAMdT3I67IyUNXw==, - } - engines: { node: '>=12' } - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/linux-loong64': 0.15.7 - esbuild-android-64: 0.15.7 - esbuild-android-arm64: 0.15.7 - esbuild-darwin-64: 0.15.7 - esbuild-darwin-arm64: 0.15.7 - esbuild-freebsd-64: 0.15.7 - esbuild-freebsd-arm64: 0.15.7 - esbuild-linux-32: 0.15.7 - esbuild-linux-64: 0.15.7 - esbuild-linux-arm: 0.15.7 - esbuild-linux-arm64: 0.15.7 - esbuild-linux-mips64le: 0.15.7 - esbuild-linux-ppc64le: 0.15.7 - esbuild-linux-riscv64: 0.15.7 - esbuild-linux-s390x: 0.15.7 - esbuild-netbsd-64: 0.15.7 - esbuild-openbsd-64: 0.15.7 - esbuild-sunos-64: 0.15.7 - esbuild-windows-32: 0.15.7 - esbuild-windows-64: 0.15.7 - esbuild-windows-arm64: 0.15.7 - dev: true - - /escalade/3.1.1: - resolution: - { - integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==, - } - engines: { node: '>=6' } - dev: true - - /escape-html/1.0.3: - resolution: - { - integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==, - } - dev: true - - /escape-string-regexp/1.0.5: - resolution: - { - integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==, - } - engines: { node: '>=0.8.0' } - dev: true - - /escape-string-regexp/4.0.0: - resolution: - { - integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, - } - engines: { node: '>=10' } - - /escodegen/2.0.0: - resolution: - { - integrity: sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==, - } - engines: { node: '>=6.0' } - hasBin: true + /has@1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} dependencies: - esprima: 4.0.1 - estraverse: 5.3.0 - esutils: 2.0.3 - optionator: 0.8.3 - optionalDependencies: - source-map: 0.6.1 + function-bind: 1.1.1 dev: true - /eslint-config-prettier/8.5.0_eslint@8.23.0: - resolution: - { - integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==, - } - hasBin: true - peerDependencies: - eslint: '>=7.0.0' + /hasha@5.2.2: + resolution: {integrity: sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==} + engines: {node: '>=8'} dependencies: - eslint: 8.23.0 + is-stream: 2.0.1 + type-fest: 0.8.1 dev: true - /eslint-plugin-cypress/2.12.1_eslint@8.23.0: - resolution: - { - integrity: sha512-c2W/uPADl5kospNDihgiLc7n87t5XhUbFDoTl6CfVkmG+kDAb5Ux10V9PoLPu9N+r7znpc+iQlcmAqT1A/89HA==, - } - peerDependencies: - eslint: '>= 3.2.1' + /hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} dependencies: - eslint: 8.23.0 - globals: 11.12.0 + function-bind: 1.1.2 dev: true - /eslint-plugin-svelte3/3.4.1_frfkdjwsrgdj4v6yq5by2hqlde: - resolution: - { - integrity: sha512-7p59WG8qV8L6wLdl4d/c3mdjkgVglQCdv5XOTk/iNPBKXuuV+Q0eFP5Wa6iJd/G2M1qR3BkLPEzaANOqKAZczw==, - } - engines: { node: '>=10' } - peerDependencies: - eslint: '>=6.0.0' - svelte: ^3.2.0 + /hast-util-embedded@2.0.1: + resolution: {integrity: sha512-QUdSOP1/o+/TxXtpPFXR2mUg2P+ySrmlX7QjwHZCXqMFyYk7YmcGSvqRW+4XgXAoHifdE1t2PwFaQK33TqVjSw==} dependencies: - eslint: 8.23.0 - svelte: 3.52.0 + hast-util-is-element: 2.1.3 dev: true - /eslint-plugin-vitest/0.0.8_pyvvhc3zqdua4akflcggygkl44: - resolution: - { - integrity: sha512-6xn2b4Cspn7Qc0v/ZMCq13fmsucnkEeUeKCna57M5vayvreshHEUFVgjtsksnyODlHIm9oyVfNfSFkToYz/dJg==, - } - engines: { node: 14.x || >= 16 } - peerDependencies: - eslint: '>=8.0.0' + /hast-util-from-parse5@7.1.2: + resolution: {integrity: sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==} dependencies: - '@typescript-eslint/utils': 5.30.0_pyvvhc3zqdua4akflcggygkl44 - eslint: 8.23.0 - transitivePeerDependencies: - - supports-color - - typescript + '@types/hast': 2.3.5 + '@types/unist': 2.0.8 + hastscript: 7.2.0 + property-information: 6.2.0 + vfile: 5.3.7 + vfile-location: 4.1.0 + web-namespaces: 2.0.1 dev: true - /eslint-scope/5.1.1: - resolution: - { - integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==, - } - engines: { node: '>=8.0.0' } + /hast-util-from-parse5@8.0.1: + resolution: {integrity: sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==} dependencies: - esrecurse: 4.3.0 - estraverse: 4.3.0 + '@types/hast': 3.0.4 + '@types/unist': 3.0.0 + devlop: 1.1.0 + hastscript: 8.0.0 + property-information: 6.2.0 + vfile: 6.0.1 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + dev: false + + /hast-util-has-property@2.0.1: + resolution: {integrity: sha512-X2+RwZIMTMKpXUzlotatPzWj8bspCymtXH3cfG3iQKV+wPF53Vgaqxi/eLqGck0wKq1kS9nvoB1wchbCPEL8sg==} dev: true - /eslint-scope/7.1.1: - resolution: - { - integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + /hast-util-is-body-ok-link@2.0.0: + resolution: {integrity: sha512-S58hCexyKdD31vMsErvgLfflW6vYWo/ixRLPJTtkOvLld24vyI8vmYmkgLA5LG3la2ME7nm7dLGdm48gfLRBfw==} dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 + '@types/hast': 2.3.5 + hast-util-has-property: 2.0.1 + hast-util-is-element: 2.1.3 dev: true - /eslint-utils/3.0.0_eslint@8.23.0: - resolution: - { - integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==, - } - engines: { node: ^10.0.0 || ^12.0.0 || >= 14.0.0 } - peerDependencies: - eslint: '>=5' + /hast-util-is-element@2.1.3: + resolution: {integrity: sha512-O1bKah6mhgEq2WtVMk+Ta5K7pPMqsBBlmzysLdcwKVrqzZQ0CHqUPiIVspNhAG1rvxpvJjtGee17XfauZYKqVA==} dependencies: - eslint: 8.23.0 - eslint-visitor-keys: 2.1.0 + '@types/hast': 2.3.5 + '@types/unist': 2.0.8 dev: true - /eslint-visitor-keys/2.1.0: - resolution: - { - integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==, - } - engines: { node: '>=10' } + /hast-util-parse-selector@3.1.1: + resolution: {integrity: sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==} + dependencies: + '@types/hast': 2.3.5 dev: true - /eslint-visitor-keys/3.3.0: - resolution: - { - integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - dev: true + /hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + dependencies: + '@types/hast': 3.0.4 + dev: false - /eslint/8.23.0: - resolution: - { - integrity: sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - hasBin: true + /hast-util-phrasing@2.0.2: + resolution: {integrity: sha512-yGkCfPkkfCyiLfK6KEl/orMDr/zgCnq/NaO9HfULx6/Zga5fso5eqQA5Ov/JZVqACygvw9shRYWgXNcG2ilo7w==} + dependencies: + '@types/hast': 2.3.5 + hast-util-embedded: 2.0.1 + hast-util-has-property: 2.0.1 + hast-util-is-body-ok-link: 2.0.0 + hast-util-is-element: 2.1.3 + dev: true + + /hast-util-raw@7.2.3: + resolution: {integrity: sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==} + dependencies: + '@types/hast': 2.3.5 + '@types/parse5': 6.0.3 + hast-util-from-parse5: 7.1.2 + hast-util-to-parse5: 7.1.0 + html-void-elements: 2.0.1 + parse5: 6.0.1 + unist-util-position: 4.0.4 + unist-util-visit: 4.1.2 + vfile: 5.3.7 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + dev: true + + /hast-util-raw@9.0.4: + resolution: {integrity: sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==} + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.0 + '@ungap/structured-clone': 1.2.0 + hast-util-from-parse5: 8.0.1 + hast-util-to-parse5: 8.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + parse5: 7.1.2 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.1 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + dev: false + + /hast-util-sanitize@5.0.1: + resolution: {integrity: sha512-IGrgWLuip4O2nq5CugXy4GI2V8kx4sFVy5Hd4vF7AR2gxS0N9s7nEAVUyeMtZKZvzrxVsHt73XdTsno1tClIkQ==} dependencies: - '@eslint/eslintrc': 1.3.1 - '@humanwhocodes/config-array': 0.10.4 - '@humanwhocodes/gitignore-to-minimatch': 1.0.2 - '@humanwhocodes/module-importer': 1.0.1 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.4 - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.1.1 - eslint-utils: 3.0.0_eslint@8.23.0 - eslint-visitor-keys: 3.3.0 - espree: 9.4.0 - esquery: 1.4.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - functional-red-black-tree: 1.0.1 - glob-parent: 6.0.2 - globals: 13.15.0 - globby: 11.1.0 - grapheme-splitter: 1.0.4 - ignore: 5.2.0 - import-fresh: 3.3.0 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - js-yaml: 4.1.0 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.1 - regexpp: 3.2.0 - strip-ansi: 6.0.1 - strip-json-comments: 3.1.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - dev: true + '@types/hast': 3.0.4 + '@ungap/structured-clone': 1.2.0 + unist-util-position: 5.0.0 + dev: false + + /hast-util-to-html@8.0.4: + resolution: {integrity: sha512-4tpQTUOr9BMjtYyNlt0P50mH7xj0Ks2xpo8M943Vykljf99HW6EzulIoJP1N3eKOSScEHzyzi9dm7/cn0RfGwA==} + dependencies: + '@types/hast': 2.3.5 + '@types/unist': 2.0.8 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-raw: 7.2.3 + hast-util-whitespace: 2.0.1 + html-void-elements: 2.0.1 + property-information: 6.2.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.3 + zwitch: 2.0.4 + dev: true + + /hast-util-to-html@9.0.1: + resolution: {integrity: sha512-hZOofyZANbyWo+9RP75xIDV/gq+OUKx+T46IlwERnKmfpwp81XBFbT9mi26ws+SJchA4RVUQwIBJpqEOBhMzEQ==} + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.0 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-raw: 9.0.4 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + property-information: 6.2.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.3 + zwitch: 2.0.4 + dev: false - /espree/9.4.0: - resolution: - { - integrity: sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + /hast-util-to-parse5@7.1.0: + resolution: {integrity: sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==} dependencies: - acorn: 8.8.0 - acorn-jsx: 5.3.2_acorn@8.8.0 - eslint-visitor-keys: 3.3.0 + '@types/hast': 2.3.5 + comma-separated-tokens: 2.0.3 + property-information: 6.2.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 dev: true - /esprima/4.0.1: - resolution: - { - integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==, - } - engines: { node: '>=4' } - hasBin: true + /hast-util-to-parse5@8.0.0: + resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==} + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 6.2.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + dev: false + + /hast-util-whitespace@2.0.1: + resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} dev: true - /esquery/1.4.0: - resolution: - { - integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==, - } - engines: { node: '>=0.10' } + /hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} dependencies: - estraverse: 5.3.0 - dev: true + '@types/hast': 3.0.4 + dev: false - /esrecurse/4.3.0: - resolution: - { - integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==, - } - engines: { node: '>=4.0' } + /hastscript@7.2.0: + resolution: {integrity: sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==} dependencies: - estraverse: 5.3.0 + '@types/hast': 2.3.5 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 3.1.1 + property-information: 6.2.0 + space-separated-tokens: 2.0.2 dev: true - /estraverse/4.3.0: - resolution: - { - integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==, - } - engines: { node: '>=4.0' } - dev: true + /hastscript@8.0.0: + resolution: {integrity: sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==} + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 6.2.0 + space-separated-tokens: 2.0.2 + dev: false + + /hastscript@9.0.0: + resolution: {integrity: sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw==} + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 6.2.0 + space-separated-tokens: 2.0.2 + dev: false - /estraverse/5.3.0: - resolution: - { - integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==, - } - engines: { node: '>=4.0' } + /heap-js@2.3.0: + resolution: {integrity: sha512-E5303mzwQ+4j/n2J0rDvEPBN7GKjhis10oHiYOgjxsmxYgqG++hz9NyLLOXttzH8as/DyiBHYpUrJTZWYaMo8Q==} + engines: {node: '>=10.0.0'} dev: true - /estree-walker/0.6.1: - resolution: - { - integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==, - } + /highlight.js@11.11.1: + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} + engines: {node: '>=12.0.0'} dev: true - /estree-walker/2.0.2: - resolution: - { - integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==, - } + /homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} + dependencies: + parse-passwd: 1.0.0 dev: true - /esutils/2.0.3: - resolution: - { - integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, - } - engines: { node: '>=0.10.0' } + /hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + dependencies: + lru-cache: 6.0.0 dev: true - /eventemitter2/6.4.5: - resolution: - { - integrity: sha512-bXE7Dyc1i6oQElDG0jMRZJrRAn9QR2xyyFGmBdZleNmyQX0FqGYmhZIrIrpPfm/w//LTo4tVQGOGQcGCb5q9uw==, - } + /html-encoding-sniffer@3.0.0: + resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} + engines: {node: '>=12'} + dependencies: + whatwg-encoding: 2.0.0 dev: true - /events/3.3.0: - resolution: - { - integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==, - } - engines: { node: '>=0.8.x' } + /html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} dev: true - /execa/4.1.0: - resolution: - { - integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==, - } - engines: { node: '>=10' } - dependencies: - cross-spawn: 7.0.3 - get-stream: 5.2.0 - human-signals: 1.1.1 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 + /html-tags@3.3.1: + resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} + engines: {node: '>=8'} dev: true - /execa/6.1.0: - resolution: - { - integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==, - } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } - dependencies: - cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 3.0.1 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.1.0 - onetime: 6.0.0 - signal-exit: 3.0.7 - strip-final-newline: 3.0.0 + /html-void-elements@2.0.1: + resolution: {integrity: sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==} dev: true - /execall/2.0.0: - resolution: - { - integrity: sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==, - } - engines: { node: '>=8' } + /html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + dev: false + + /html-whitespace-sensitive-tag-names@2.0.0: + resolution: {integrity: sha512-SQdIvTTtnHAx72xGUIUudvVOCjeWvV1U7rvSFnNGxTGRw3ZC7RES4Gw6dm1nMYD60TXvm6zjk/bWqgNc5pjQaw==} + dev: true + + /htmlparser2-svelte@4.1.0: + resolution: {integrity: sha512-+4f4RBFz7Rj2Hp0ZbFbXC+Kzbd6S9PgjiuFtdT76VMNgKogrEZy0pG2UrPycPbrZzVEIM5lAT3lAdkSTCHLPjg==} dependencies: - clone-regexp: 2.2.0 + domelementtype: 2.3.0 + domhandler: 3.3.0 + domutils: 2.8.0 + entities: 2.2.0 dev: true - /executable/4.1.1: - resolution: - { - integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==, - } - engines: { node: '>=4' } + /htmlparser2@3.10.1: + resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==} dependencies: - pify: 2.3.0 + domelementtype: 1.3.1 + domhandler: 2.4.2 + domutils: 1.7.0 + entities: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.0 dev: true - /extend-shallow/2.0.1: - resolution: - { - integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==, - } - engines: { node: '>=0.10.0' } + /htmlparser2@8.0.1: + resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==} dependencies: - is-extendable: 0.1.1 + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.0.1 + entities: 4.4.0 dev: true - /extend/3.0.2: - resolution: - { - integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==, - } + /http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 dev: true - /extract-zip/2.0.1_supports-color@8.1.1: - resolution: - { - integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==, - } - engines: { node: '>= 10.17.0' } - hasBin: true + /http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} dependencies: - debug: 4.3.4_supports-color@8.1.1 - get-stream: 5.2.0 - yauzl: 2.10.0 - optionalDependencies: - '@types/yauzl': 2.10.0 + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.4.0 transitivePeerDependencies: - supports-color dev: true - /extsprintf/1.3.0: - resolution: - { - integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==, - } - engines: { '0': node >=0.6.0 } - dev: true - - /fast-deep-equal/3.1.3: - resolution: - { - integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, - } - dev: true - - /fast-glob/3.2.11: - resolution: - { - integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==, - } - engines: { node: '>=8.6.0' } + /https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 + agent-base: 6.0.2 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color dev: true - /fast-json-stable-stringify/2.1.0: - resolution: - { - integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, - } + /human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} dev: true - /fast-levenshtein/2.0.6: - resolution: - { - integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, - } + /human-signals@3.0.1: + resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==} + engines: {node: '>=12.20.0'} dev: true - /fastest-levenshtein/1.0.12: - resolution: - { - integrity: sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==, - } + /husky@8.0.3: + resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} + engines: {node: '>=14'} + hasBin: true dev: true - /fastq/1.13.0: - resolution: - { - integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==, - } + /i18next-browser-languagedetector@7.0.1: + resolution: {integrity: sha512-Pa5kFwaczXJAeHE56CHG2aWzFBMJNUNghf0Pm4SwSrEMps/PTKqW90EYWlIvhuYStf3Sn1K0vw+gH3+TLdkH1g==} dependencies: - reusify: 1.0.4 - dev: true + '@babel/runtime': 7.20.7 + dev: false - /fd-slicer/1.1.0: - resolution: - { - integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==, - } + /i18next@22.4.15: + resolution: {integrity: sha512-yYudtbFrrmWKLEhl6jvKUYyYunj4bTBCe2qIUYAxbXoPusY7YmdwPvOE6fx6UIfWvmlbCWDItr7wIs8KEBZ5Zg==} dependencies: - pend: 1.2.0 - dev: true + '@babel/runtime': 7.20.7 + dev: false - /figures/3.2.0: - resolution: - { - integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==, - } - engines: { node: '>=8' } + /iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} dependencies: - escape-string-regexp: 1.0.5 + safer-buffer: 2.1.2 dev: true - /file-entry-cache/6.0.1: - resolution: - { - integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==, - } - engines: { node: ^10.12.0 || >=12.0.0 } + /iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} dependencies: - flat-cache: 3.0.4 + safer-buffer: 2.1.2 dev: true - /file-uri-to-path/1.0.0: - resolution: - { - integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==, - } + /ignore@4.0.6: + resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} + engines: {node: '>= 4'} dev: true - /fill-range/7.0.1: - resolution: - { - integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==, - } - engines: { node: '>=8' } - dependencies: - to-regex-range: 5.0.1 + /ignore@5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} dev: true - /finalhandler/1.1.2: - resolution: - { - integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==, - } - engines: { node: '>= 0.8' } + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} dependencies: - debug: 2.6.9 - encodeurl: 1.0.2 - escape-html: 1.0.3 - on-finished: 2.3.0 - parseurl: 1.3.3 - statuses: 1.5.0 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /import-lazy@4.0.0: + resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} + engines: {node: '>=8'} dev: true - /find-cache-dir/3.3.2: - resolution: - { - integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==, - } - engines: { node: '>=8' } + /import-local@3.1.0: + resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} + engines: {node: '>=8'} + hasBin: true dependencies: - commondir: 1.0.1 - make-dir: 3.1.0 pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 dev: true - /find-up/4.1.0: - resolution: - { - integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==, - } - engines: { node: '>=8' } - dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 + /import-meta-resolve@4.1.0: + resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} + + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} dev: true - /find-up/5.0.0: - resolution: - { - integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==, - } - engines: { node: '>=10' } - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 + /indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + dev: true + + /indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} dev: true - /flat-cache/3.0.4: - resolution: - { - integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==, - } - engines: { node: ^10.12.0 || >=12.0.0 } + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} dependencies: - flatted: 3.2.5 - rimraf: 3.0.2 + once: 1.4.0 + wrappy: 1.0.2 dev: true - /flatted/3.2.5: - resolution: - { - integrity: sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==, - } + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: true - /flexsearch/0.7.21: - resolution: - { - integrity: sha512-W7cHV7Hrwjid6lWmy0IhsWDFQboWSng25U3VVywpHOTJnnAZNPScog67G+cVpeX9f7yDD21ih0WDrMMT+JoaYg==, - } + /ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} dev: true - /foreground-child/2.0.0: - resolution: - { - integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==, - } - engines: { node: '>=8.0.0' } + /internal-slot@1.0.4: + resolution: {integrity: sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==} + engines: {node: '>= 0.4'} dependencies: - cross-spawn: 7.0.3 - signal-exit: 3.0.7 + get-intrinsic: 1.2.4 + has: 1.0.3 + side-channel: 1.0.6 dev: true - /forever-agent/0.6.1: - resolution: - { - integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==, - } + /internal-slot@1.0.5: + resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.4 + has: 1.0.3 + side-channel: 1.0.6 dev: true - /form-data/2.3.3: - resolution: - { - integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==, - } - engines: { node: '>= 0.12' } - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 + /ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} dev: true - /form-data/4.0.0: - resolution: - { - integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==, - } - engines: { node: '>= 6' } + /is-arguments@1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + engines: {node: '>= 0.4'} dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 + call-bind: 1.0.7 + has-tostringtag: 1.0.0 dev: true - /fraction.js/4.2.0: - resolution: - { - integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==, - } + /is-array-buffer@3.0.1: + resolution: {integrity: sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==} + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + is-typed-array: 1.1.10 dev: true - /fs-extra/10.1.0: - resolution: - { - integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==, - } - engines: { node: '>=12' } + /is-array-buffer@3.0.2: + resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} dependencies: - graceful-fs: 4.2.10 - jsonfile: 6.1.0 - universalify: 2.0.0 + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + is-typed-array: 1.1.10 + dev: true + + /is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} dev: true - /fs-extra/9.1.0: - resolution: - { - integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==, - } - engines: { node: '>=10' } + /is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} dependencies: - at-least-node: 1.0.0 - graceful-fs: 4.2.10 - jsonfile: 6.1.0 - universalify: 2.0.0 + has-bigints: 1.0.2 dev: true - /fs-minipass/2.1.0: - resolution: - { - integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==, - } - engines: { node: '>= 8' } + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} dependencies: - minipass: 3.3.4 + binary-extensions: 2.2.0 dev: true - /fs.realpath/1.0.0: - resolution: - { - integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==, - } + /is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.0 dev: true - /fsevents/2.3.2: - resolution: - { - integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==, - } - engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } - os: [darwin] - requiresBuild: true + /is-buffer@2.0.5: + resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} + engines: {node: '>=4'} + + /is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} dev: true - optional: true - /function-bind/1.1.1: - resolution: - { - integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==, - } + /is-core-module@2.13.0: + resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} + dependencies: + has: 1.0.3 + dev: true - /function.prototype.name/1.1.5: - resolution: - { - integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==, - } - engines: { node: '>= 0.4' } + /is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.20.1 - functions-have-names: 1.2.3 - dev: false + has-tostringtag: 1.0.0 + dev: true - /functional-red-black-tree/1.0.1: - resolution: - { - integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==, - } + /is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true dev: true - /functions-have-names/1.2.3: - resolution: - { - integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==, - } - dev: false + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true - /gauge/3.0.2: - resolution: - { - integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==, - } - engines: { node: '>=10' } - dependencies: - aproba: 2.0.0 - color-support: 1.1.3 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - object-assign: 4.1.1 - signal-exit: 3.0.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wide-align: 1.1.5 + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} dev: true - /gensync/1.0.0-beta.2: - resolution: - { - integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==, - } - engines: { node: '>=6.9.0' } + /is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} dev: true - /get-caller-file/2.0.5: - resolution: - { - integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==, - } - engines: { node: 6.* || 8.* || >= 10.* } + /is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} dev: true - /get-func-name/2.0.0: - resolution: - { - integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==, - } + /is-generator-function@1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 dev: true - /get-intrinsic/1.1.1: - resolution: - { - integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==, - } + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} dependencies: - function-bind: 1.1.1 - has: 1.0.3 - has-symbols: 1.0.3 - dev: false + is-extglob: 2.1.1 + dev: true - /get-port/3.2.0: - resolution: - { - integrity: sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==, - } - engines: { node: '>=4' } + /is-negative-zero@2.0.2: + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} dev: true - /get-stdin/8.0.0: - resolution: - { - integrity: sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==, - } - engines: { node: '>=10' } + /is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 dev: true - /get-stdin/9.0.0: - resolution: - { - integrity: sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==, - } - engines: { node: '>=12' } + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} dev: true - /get-stream/5.2.0: - resolution: - { - integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==, - } - engines: { node: '>=8' } - dependencies: - pump: 3.0.0 + /is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} dev: true - /get-stream/6.0.1: - resolution: - { - integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==, - } - engines: { node: '>=10' } + /is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} dev: true - /get-symbol-description/1.0.0: - resolution: - { - integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==, - } - engines: { node: '>= 0.4' } - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.1.1 - dev: false + /is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} - /getos/3.2.1: - resolution: - { - integrity: sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==, - } - dependencies: - async: 3.2.3 + /is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} dev: true - /getpass/0.1.7: - resolution: - { - integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==, - } - dependencies: - assert-plus: 1.0.0 + /is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} dev: true - /glob-parent/5.1.2: - resolution: - { - integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==, - } - engines: { node: '>= 6' } + /is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} dependencies: - is-glob: 4.0.3 + '@types/estree': 1.0.6 + + /is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.0 dev: true - /glob-parent/6.0.2: - resolution: - { - integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, - } - engines: { node: '>=10.13.0' } + /is-shared-array-buffer@1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} dependencies: - is-glob: 4.0.3 + call-bind: 1.0.7 dev: true - /glob-to-regexp/0.4.1: - resolution: - { - integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==, - } + /is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} dev: true - /glob/7.2.3: - resolution: - { - integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==, - } - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 + /is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: true - /global-dirs/3.0.0: - resolution: - { - integrity: sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==, - } - engines: { node: '>=10' } + /is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} dependencies: - ini: 2.0.0 + has-tostringtag: 1.0.0 dev: true - /global-modules/2.0.0: - resolution: - { - integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==, - } - engines: { node: '>=6' } + /is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} dependencies: - global-prefix: 3.0.0 + has-symbols: 1.0.3 dev: true - /global-prefix/3.0.0: - resolution: - { - integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==, - } - engines: { node: '>=6' } + /is-typed-array@1.1.10: + resolution: {integrity: sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==} + engines: {node: '>= 0.4'} dependencies: - ini: 1.3.8 - kind-of: 6.0.3 - which: 1.3.1 + available-typed-arrays: 1.0.5 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 dev: true - /globals/11.12.0: - resolution: - { - integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==, - } - engines: { node: '>=4' } + /is-typedarray@1.0.0: + resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} dev: true - /globals/13.15.0: - resolution: - { - integrity: sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==, - } - engines: { node: '>=8' } + /is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: - type-fest: 0.20.2 + call-bind: 1.0.7 dev: true - /globalyzer/0.1.0: - resolution: - { - integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==, - } + /is-windows@0.2.0: + resolution: {integrity: sha512-n67eJYmXbniZB7RF4I/FTjK1s6RPOCTxhYrVYLRaCt3lF0mpWZPKr3T2LSZAqyjQsxR2qMmGYXXzK0YWwcPM1Q==} + engines: {node: '>=0.10.0'} dev: true - /globby/11.1.0: - resolution: - { - integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==, - } - engines: { node: '>=10' } - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.2.11 - ignore: 5.2.0 - merge2: 1.4.1 - slash: 3.0.0 + /is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} dev: true - /globby/12.2.0: - resolution: - { - integrity: sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==, - } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + /is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} dependencies: - array-union: 3.0.1 - dir-glob: 3.0.1 - fast-glob: 3.2.11 - ignore: 5.2.0 - merge2: 1.4.1 - slash: 4.0.0 + is-docker: 2.2.1 dev: true - /globby/13.1.2: - resolution: - { - integrity: sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==, - } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } - dependencies: - dir-glob: 3.0.1 - fast-glob: 3.2.11 - ignore: 5.2.0 - merge2: 1.4.1 - slash: 4.0.0 + /isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} dev: true - /globjoin/0.1.4: - resolution: - { - integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==, - } + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true - /globrex/0.1.2: - resolution: - { - integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==, - } + /isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} dev: true - /google-protobuf/3.20.1: - resolution: - { - integrity: sha512-XMf1+O32FjYIV3CYu6Tuh5PNbfNEU5Xu22X+Xkdb/DUexFlCzhvv7d5Iirm4AOwn8lv4al1YvIhzGrg2j9Zfzw==, - } + /istanbul-lib-coverage@3.2.0: + resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} + engines: {node: '>=8'} dev: true - /graceful-fs/4.2.10: - resolution: - { - integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==, - } + /istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} dev: true - /grapheme-splitter/1.0.4: - resolution: - { - integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==, - } + /istanbul-lib-hook@3.0.0: + resolution: {integrity: sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==} + engines: {node: '>=8'} + dependencies: + append-transform: 2.0.0 dev: true - /gray-matter/4.0.3: - resolution: - { - integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==, - } - engines: { node: '>=6.0' } + /istanbul-lib-instrument@4.0.3: + resolution: {integrity: sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==} + engines: {node: '>=8'} dependencies: - js-yaml: 3.14.1 - kind-of: 6.0.3 - section-matter: 1.0.0 - strip-bom-string: 1.0.0 + '@babel/core': 7.24.4 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color dev: true - /happy-dom/2.55.0: - resolution: - { - integrity: sha512-CHDMBRau+l/yKQL+ANmexRAC8FRCuYbXRSpu/GbLVyfqkrlBzV7OSNd5C5HZ+pVFtFv1bFJYC5r+xrqgGQuq5w==, - } + /istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} dependencies: - css.escape: 1.5.1 - he: 1.2.0 - node-fetch: 2.6.7 - sync-request: 6.1.0 - webidl-conversions: 7.0.0 - whatwg-encoding: 2.0.0 - whatwg-mimetype: 3.0.0 + '@babel/core': 7.24.4 + '@babel/parser': 7.26.9 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 transitivePeerDependencies: - - encoding + - supports-color dev: true - /hard-rejection/2.1.0: - resolution: - { - integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==, - } - engines: { node: '>=6' } + /istanbul-lib-instrument@6.0.2: + resolution: {integrity: sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==} + engines: {node: '>=10'} + dependencies: + '@babel/core': 7.24.4 + '@babel/parser': 7.26.9 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color dev: true - /has-bigints/1.0.2: - resolution: - { - integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==, - } - dev: false - - /has-flag/3.0.0: - resolution: - { - integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==, - } - engines: { node: '>=4' } + /istanbul-lib-processinfo@2.0.3: + resolution: {integrity: sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==} + engines: {node: '>=8'} + dependencies: + archy: 1.0.0 + cross-spawn: 7.0.6 + istanbul-lib-coverage: 3.2.2 + p-map: 3.0.0 + rimraf: 3.0.2 + uuid: 8.3.2 dev: true - /has-flag/4.0.0: - resolution: - { - integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, - } - engines: { node: '>=8' } + /istanbul-lib-report@3.0.0: + resolution: {integrity: sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==} + engines: {node: '>=8'} + dependencies: + istanbul-lib-coverage: 3.2.0 + make-dir: 3.1.0 + supports-color: 7.2.0 dev: true - /has-property-descriptors/1.0.0: - resolution: - { - integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==, - } + /istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} dependencies: - get-intrinsic: 1.1.1 - dev: false - - /has-symbols/1.0.3: - resolution: - { - integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==, - } - engines: { node: '>= 0.4' } - dev: false + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + dev: true - /has-tostringtag/1.0.0: - resolution: - { - integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==, - } - engines: { node: '>= 0.4' } + /istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} dependencies: - has-symbols: 1.0.3 - dev: false + debug: 4.4.0 + istanbul-lib-coverage: 3.2.2 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + dev: true - /has-unicode/2.0.1: - resolution: - { - integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==, - } + /istanbul-reports@3.1.5: + resolution: {integrity: sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==} + engines: {node: '>=8'} + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.0 dev: true - /has/1.0.3: - resolution: - { - integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==, - } - engines: { node: '>= 0.4.0' } + /istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} dependencies: - function-bind: 1.1.1 + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + dev: true - /he/1.2.0: - resolution: - { - integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==, - } - hasBin: true + /jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 dev: true - /header-case/2.0.4: - resolution: - { - integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==, - } + /jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - capital-case: 1.0.4 - tslib: 2.4.0 + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 dev: true - /highlight.js/11.2.0: - resolution: - { - integrity: sha512-JOySjtOEcyG8s4MLR2MNbLUyaXqUunmSnL2kdV/KuGJOmHZuAR5xC54Ko7goAXBWNhf09Vy3B+U7vR62UZ/0iw==, - } - engines: { node: '>=12.0.0' } + /jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.15.3 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.5.3 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.1.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color dev: true - /histoire/0.11.3_vite@3.1.0: - resolution: - { - integrity: sha512-mfmzzk8HPhjQg/4Loiqz07BS5O6sbcoi4gCKzqa27NQHzEjhlx0i+nPbylUH3kVBYFXeUUlQ5aW7WX+Ix2hU3w==, - } + /jest-cli@29.7.0(@types/node@18.15.3): + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true peerDependencies: - vite: ^2.9.0 || ^3.0.0 + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true dependencies: - '@histoire/app': 0.11.3_vite@3.1.0 - '@histoire/controls': 0.11.3 - '@histoire/shared': 0.11.3_vite@3.1.0 - '@histoire/vendors': 0.11.3 - '@types/flexsearch': 0.7.3 - '@types/markdown-it': 12.2.3 - birpc: 0.1.1 - change-case: 4.1.2 - chokidar: 3.5.3 - connect: 3.7.0 - defu: 6.1.0 - diacritics: 1.3.0 - flexsearch: 0.7.21 - fs-extra: 10.1.0 - globby: 13.1.2 - gray-matter: 4.0.3 - happy-dom: 2.55.0 - jiti: 1.16.0 - markdown-it: 12.3.2 - markdown-it-anchor: 8.6.5_2zb4u3vubltivolgu556vv4aom - markdown-it-attrs: 4.1.4_markdown-it@12.3.2 - markdown-it-emoji: 2.0.2 + '@jest/core': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@18.15.3) + exit: 0.1.2 + import-local: 3.1.0 + jest-config: 29.7.0(@types/node@18.15.3) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true + + /jest-config@29.7.0(@types/node@18.15.3): + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.24.4 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.15.3 + babel-jest: 29.7.0(@babel/core@7.24.4) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 micromatch: 4.0.5 - mrmime: 1.0.1 - pathe: 0.2.0 - picocolors: 1.0.0 - sade: 1.8.1 - shiki: 0.10.1 - sirv: 2.0.2 - tinypool: 0.1.3 - vite: 3.1.0 - vite-node: 0.23.4 - vite-plugin-inspect: 0.7.5_vite@3.1.0 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 transitivePeerDependencies: - - encoding - - less - - sass - - stylus + - babel-plugin-macros - supports-color - - terser dev: true - /hosted-git-info/2.8.9: - resolution: - { - integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==, - } + /jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 dev: true - /hosted-git-info/4.1.0: - resolution: - { - integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==, - } - engines: { node: '>=10' } + /jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - lru-cache: 6.0.0 + detect-newline: 3.1.0 + dev: true + + /jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 dev: true - /html-encoding-sniffer/3.0.0: - resolution: - { - integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==, - } - engines: { node: '>=12' } + /jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - whatwg-encoding: 2.0.0 + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.15.3 + jest-mock: 29.7.0 + jest-util: 29.7.0 dev: true - /html-escaper/2.0.2: - resolution: - { - integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==, - } + /jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true - /html-tags/3.2.0: - resolution: - { - integrity: sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==, - } - engines: { node: '>=8' } + /jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 18.15.3 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.5 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 dev: true - /htmlparser2/6.1.0: - resolution: - { - integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==, - } + /jest-junit@16.0.0: + resolution: {integrity: sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==} + engines: {node: '>=10.12.0'} dependencies: - domelementtype: 2.3.0 - domhandler: 4.3.1 - domutils: 2.8.0 - entities: 2.2.0 + mkdirp: 1.0.4 + strip-ansi: 6.0.1 + uuid: 8.3.2 + xml: 1.0.1 + dev: true - /http-basic/8.1.3: - resolution: - { - integrity: sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==, - } - engines: { node: '>=6.0.0' } + /jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - caseless: 0.12.0 - concat-stream: 1.6.2 - http-response-object: 3.0.2 - parse-cache-control: 1.0.1 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 dev: true - /http-proxy-agent/5.0.0: - resolution: - { - integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==, - } - engines: { node: '>= 6' } + /jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@tootallnate/once': 2.0.0 - agent-base: 6.0.2 - debug: 4.3.4 - transitivePeerDependencies: - - supports-color + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 dev: true - /http-response-object/3.0.2: - resolution: - { - integrity: sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==, - } + /jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 10.17.60 + '@babel/code-frame': 7.24.7 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.5 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 dev: true - /http-signature/1.3.6: - resolution: - { - integrity: sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==, - } - engines: { node: '>=0.10' } + /jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - assert-plus: 1.0.0 - jsprim: 2.0.2 - sshpk: 1.17.0 + '@jest/types': 29.6.3 + '@types/node': 18.15.3 + jest-util: 29.7.0 dev: true - /https-proxy-agent/5.0.1: - resolution: - { - integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==, - } - engines: { node: '>= 6' } - dependencies: - agent-base: 6.0.2 - debug: 4.3.4 + /jest-playwright-preset@4.0.0(jest-circus@29.7.0)(jest-environment-node@29.7.0)(jest-runner@29.7.0)(jest@29.7.0): + resolution: {integrity: sha512-+dGZ1X2KqtwXaabVjTGxy0a3VzYfvYsWaRcuO8vMhyclHSOpGSI1+5cmlqzzCwQ3+fv0EjkTc7I5aV9lo08dYw==} + peerDependencies: + jest: ^29.3.1 + jest-circus: ^29.3.1 + jest-environment-node: ^29.3.1 + jest-runner: ^29.3.1 + dependencies: + expect-playwright: 0.8.0 + jest: 29.7.0(@types/node@18.15.3) + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-process-manager: 0.4.0 + jest-runner: 29.7.0 + nyc: 15.1.0 + playwright-core: 1.50.1 + rimraf: 3.0.2 + uuid: 8.3.2 transitivePeerDependencies: + - debug - supports-color dev: true - /human-signals/1.1.1: - resolution: - { - integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==, - } - engines: { node: '>=8.12.0' } - dev: true - - /human-signals/3.0.1: - resolution: - { - integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==, - } - engines: { node: '>=12.20.0' } - dev: true - - /husky/8.0.1: - resolution: - { - integrity: sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==, - } - engines: { node: '>=14' } - hasBin: true + /jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + dependencies: + jest-resolve: 29.7.0 dev: true - /iconv-lite/0.6.3: - resolution: - { - integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==, - } - engines: { node: '>=0.10.0' } + /jest-process-manager@0.4.0: + resolution: {integrity: sha512-80Y6snDyb0p8GG83pDxGI/kQzwVTkCxc7ep5FPe/F6JYdvRDhwr6RzRmPSP7SEwuLhxo80lBS/NqOdUIbHIfhw==} dependencies: - safer-buffer: 2.1.2 + '@types/wait-on': 5.3.4 + chalk: 4.1.2 + cwd: 0.10.0 + exit: 0.1.2 + find-process: 1.4.7 + prompts: 2.4.2 + signal-exit: 3.0.7 + spawnd: 5.0.0 + tree-kill: 1.2.2 + wait-on: 7.2.0 + transitivePeerDependencies: + - debug + - supports-color dev: true - /ieee754/1.2.1: - resolution: - { - integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==, - } + /jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dev: true - /ignore/5.2.0: - resolution: - { - integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==, - } - engines: { node: '>= 4' } + /jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color dev: true - /import-fresh/3.3.0: - resolution: - { - integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==, - } - engines: { node: '>=6' } + /jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.8 + resolve.exports: 2.0.2 + slash: 3.0.0 dev: true - /import-lazy/4.0.0: - resolution: - { - integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==, - } - engines: { node: '>=8' } + /jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.15.3 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color dev: true - /imurmurhash/0.1.4: - resolution: - { - integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==, - } - engines: { node: '>=0.8.19' } + /jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.15.3 + chalk: 4.1.2 + cjs-module-lexer: 1.3.1 + collect-v8-coverage: 1.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color dev: true - /indent-string/4.0.0: - resolution: - { - integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==, - } - engines: { node: '>=8' } + /jest-serializer-html@7.1.0: + resolution: {integrity: sha512-xYL2qC7kmoYHJo8MYqJkzrl/Fdlx+fat4U1AqYg+kafqwcKPiMkOcjWHPKhueuNEgr+uemhGc+jqXYiwCyRyLA==} + dependencies: + diffable-html: 4.1.0 dev: true - /inflight/1.0.6: - resolution: - { - integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==, - } + /jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - once: 1.4.0 - wrappy: 1.0.2 + '@babel/core': 7.24.4 + '@babel/generator': 7.24.7 + '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.24.4) + '@babel/plugin-syntax-typescript': 7.24.1(@babel/core@7.24.4) + '@babel/types': 7.26.9 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.4) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color dev: true - /inherits/2.0.4: - resolution: - { - integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==, - } + /jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@types/node': 18.15.3 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 dev: true - /ini/1.3.8: - resolution: - { - integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==, - } + /jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 dev: true - /ini/2.0.0: - resolution: - { - integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==, - } - engines: { node: '>=10' } + /jest-watch-typeahead@2.2.2(jest@29.7.0): + resolution: {integrity: sha512-+QgOFW4o5Xlgd6jGS5X37i08tuuXNW8X0CV9WNFi+3n8ExCIP+E1melYhvYLjv5fE6D0yyzk74vsSO8I6GqtvQ==} + engines: {node: ^14.17.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + jest: ^27.0.0 || ^28.0.0 || ^29.0.0 + dependencies: + ansi-escapes: 6.2.1 + chalk: 5.2.0 + jest: 29.7.0(@types/node@18.15.3) + jest-regex-util: 29.6.3 + jest-watcher: 29.7.0 + slash: 5.1.0 + string-length: 5.0.1 + strip-ansi: 7.1.0 dev: true - /internal-slot/1.0.3: - resolution: - { - integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==, - } - engines: { node: '>= 0.4' } + /jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - get-intrinsic: 1.1.1 - has: 1.0.3 - side-channel: 1.0.4 - dev: false - - /is-arrayish/0.2.1: - resolution: - { - integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==, - } + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.15.3 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 dev: true - /is-bigint/1.0.4: - resolution: - { - integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==, - } + /jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} dependencies: - has-bigints: 1.0.2 - dev: false + '@types/node': 18.15.3 + merge-stream: 2.0.0 + supports-color: 8.1.1 + dev: true - /is-binary-path/2.1.0: - resolution: - { - integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==, - } - engines: { node: '>=8' } + /jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - binary-extensions: 2.2.0 + '@types/node': 18.15.3 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 dev: true - /is-boolean-object/1.1.2: - resolution: - { - integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==, - } - engines: { node: '>= 0.4' } + /jest@29.7.0(@types/node@18.15.3): + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true dependencies: - call-bind: 1.0.2 - has-tostringtag: 1.0.0 - dev: false - - /is-callable/1.2.4: - resolution: - { - integrity: sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==, - } - engines: { node: '>= 0.4' } - dev: false + '@jest/core': 29.7.0 + '@jest/types': 29.6.3 + import-local: 3.1.0 + jest-cli: 29.7.0(@types/node@18.15.3) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + dev: true - /is-ci/3.0.1: - resolution: - { - integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==, - } + /jiti@1.21.0: + resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} hasBin: true - dependencies: - ci-info: 3.3.1 dev: true - /is-core-module/2.9.0: - resolution: - { - integrity: sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==, - } + /joi@17.13.1: + resolution: {integrity: sha512-vaBlIKCyo4FCUtCm7Eu4QZd/q02bWcxfUO6YSXAZOWF6gzcLBeba8kwotUdYJjDLW8Cz8RywsSOqiNJZW0mNvg==} dependencies: - has: 1.0.3 + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.5 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 dev: true - /is-date-object/1.0.5: - resolution: - { - integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==, - } - engines: { node: '>= 0.4' } + /joi@17.13.3: + resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} + requiresBuild: true dependencies: - has-tostringtag: 1.0.0 + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.5 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 dev: false + optional: true - /is-extendable/0.1.1: - resolution: - { - integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==, - } - engines: { node: '>=0.10.0' } + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true - /is-extglob/2.1.1: - resolution: - { - integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, - } - engines: { node: '>=0.10.0' } + /js-tokens@8.0.0: + resolution: {integrity: sha512-PC7MzqInq9OqKyTXfIvQNcjMkODJYC8A17kAaQgeW79yfhqTWSOfjHYQ2mDDcwJ96Iibtwkfh0C7R/OvqPlgVA==} dev: true - /is-fullwidth-code-point/3.0.0: - resolution: - { - integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==, - } - engines: { node: '>=8' } + /js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 dev: true - /is-fullwidth-code-point/4.0.0: - resolution: - { - integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==, - } - engines: { node: '>=12' } + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 dev: true - /is-glob/4.0.3: - resolution: - { - integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, - } - engines: { node: '>=0.10.0' } - dependencies: - is-extglob: 2.1.1 + /jsdoc-type-pratt-parser@4.1.0: + resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==} + engines: {node: '>=12.0.0'} dev: true - /is-installed-globally/0.4.0: - resolution: - { - integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==, - } - engines: { node: '>=10' } + /jsdom@20.0.3: + resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==} + engines: {node: '>=14'} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true dependencies: - global-dirs: 3.0.0 - is-path-inside: 3.0.3 + abab: 2.0.6 + acorn: 8.8.1 + acorn-globals: 7.0.1 + cssom: 0.5.0 + cssstyle: 2.3.0 + data-urls: 3.0.2 + decimal.js: 10.4.3 + domexception: 4.0.0 + escodegen: 2.0.0 + form-data: 4.0.0 + html-encoding-sniffer: 3.0.0 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.2 + parse5: 7.1.2 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.3 + w3c-xmlserializer: 4.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 2.0.0 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + ws: 8.17.1 + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate dev: true - /is-negative-zero/2.0.2: - resolution: - { - integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==, - } - engines: { node: '>= 0.4' } - dev: false + /jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + dev: true - /is-number-object/1.0.7: - resolution: - { - integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==, - } - engines: { node: '>= 0.4' } + /json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} dependencies: - has-tostringtag: 1.0.0 + bignumber.js: 9.1.1 dev: false - /is-number/7.0.0: - resolution: - { - integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, - } - engines: { node: '>=0.12.0' } + /json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} dev: true - /is-path-inside/3.0.3: - resolution: - { - integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==, - } - engines: { node: '>=8' } - dev: true + /json-schema-to-ts@3.1.1: + resolution: {integrity: sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==} + engines: {node: '>=16'} + requiresBuild: true + dependencies: + '@babel/runtime': 7.23.2 + ts-algebra: 2.0.0 + dev: false + optional: true - /is-plain-obj/1.1.0: - resolution: - { - integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==, - } - engines: { node: '>=0.10.0' } + /json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true - /is-plain-object/5.0.0: - resolution: - { - integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==, - } - engines: { node: '>=0.10.0' } - - /is-potential-custom-element-name/1.0.1: - resolution: - { - integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==, - } + /json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} dev: true - /is-regex/1.1.4: - resolution: - { - integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==, - } - engines: { node: '>= 0.4' } - dependencies: - call-bind: 1.0.2 - has-tostringtag: 1.0.0 - dev: false - - /is-regexp/2.1.0: - resolution: - { - integrity: sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==, - } - engines: { node: '>=6' } + /json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true - /is-shared-array-buffer/1.0.2: - resolution: - { - integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==, - } + /json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true dependencies: - call-bind: 1.0.2 - dev: false + minimist: 1.2.8 + dev: true - /is-stream/2.0.1: - resolution: - { - integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==, - } - engines: { node: '>=8' } + /json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true dev: true - /is-stream/3.0.0: - resolution: - { - integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==, - } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + /jsonc-parser@3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} dev: true - /is-string/1.0.7: - resolution: - { - integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==, - } - engines: { node: '>= 0.4' } + /jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} dependencies: - has-tostringtag: 1.0.0 - dev: false + universalify: 2.0.0 + optionalDependencies: + graceful-fs: 4.2.11 + dev: true - /is-symbol/1.0.4: - resolution: - { - integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==, - } - engines: { node: '>= 0.4' } + /junit-report-builder@5.1.1: + resolution: {integrity: sha512-ZNOIIGMzqCGcHQEA2Q4rIQQ3Df6gSIfne+X9Rly9Bc2y55KxAZu8iGv+n2pP0bLf0XAOctJZgeloC54hWzCahQ==} + engines: {node: '>=16'} dependencies: - has-symbols: 1.0.3 - dev: false - - /is-typedarray/1.0.0: - resolution: - { - integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==, - } + lodash: 4.17.21 + make-dir: 3.1.0 + xmlbuilder: 15.1.1 dev: true - /is-unicode-supported/0.1.0: - resolution: - { - integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==, - } - engines: { node: '>=10' } - dev: true + /just-debounce@1.1.0: + resolution: {integrity: sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==} + dev: false - /is-weakref/1.0.2: - resolution: - { - integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==, - } - dependencies: - call-bind: 1.0.2 + /just-throttle@4.2.0: + resolution: {integrity: sha512-/iAZv1953JcExpvsywaPKjSzfTiCLqeguUTE6+VmK15mOcwxBx7/FHrVvS4WEErMR03TRazH8kcBSHqMagYIYg==} dev: false - /isarray/1.0.0: - resolution: - { - integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==, - } + /kebab-case@1.0.2: + resolution: {integrity: sha512-7n6wXq4gNgBELfDCpzKc+mRrZFs7D+wgfF5WRFLNAr4DA/qtr9Js8uOAVAfHhuLMfAcQ0pRKqbpjx+TcJVdE1Q==} + dev: false + + /kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} dev: true - /isexe/2.0.0: - resolution: - { - integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, - } + /kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} dev: true - /isstream/0.1.2: - resolution: - { - integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==, - } + /kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + /known-css-properties@0.28.0: + resolution: {integrity: sha512-9pSL5XB4J+ifHP0e0jmmC98OGC1nL8/JjS+fi6mnTlIf//yt/MfVLtKg7S6nCtj/8KTcWX7nRlY0XywoYY1ISQ==} dev: true - /istanbul-lib-coverage/3.2.0: - resolution: - { - integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==, - } - engines: { node: '>=8' } + /known-css-properties@0.35.0: + resolution: {integrity: sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==} dev: true - /istanbul-lib-report/3.0.0: - resolution: - { - integrity: sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==, - } - engines: { node: '>=8' } - dependencies: - istanbul-lib-coverage: 3.2.0 - make-dir: 3.1.0 - supports-color: 7.2.0 + /leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} dev: true - /istanbul-reports/3.1.4: - resolution: - { - integrity: sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==, - } - engines: { node: '>=8' } + /levn@0.3.0: + resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==} + engines: {node: '>= 0.8.0'} dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.0 + prelude-ls: 1.1.2 + type-check: 0.3.2 dev: true - /jest-diff/27.5.1: - resolution: - { - integrity: sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==, - } - engines: { node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0 } + /levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} dependencies: - chalk: 4.1.2 - diff-sequences: 27.5.1 - jest-get-type: 27.5.1 - pretty-format: 27.5.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 dev: true - /jest-get-type/27.5.1: - resolution: - { - integrity: sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==, - } - engines: { node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0 } - dev: true + /libphonenumber-js@1.12.9: + resolution: {integrity: sha512-VWwAdNeJgN7jFOD+wN4qx83DTPMVPPAUyx9/TUkBXKLiNkuWWk6anV0439tgdtwaJDrEdqkvdN22iA6J4bUCZg==} + requiresBuild: true + dev: false + optional: true - /jest-websocket-mock/2.3.0: - resolution: - { - integrity: sha512-kXhRRApRdT4hLG/4rhsfcR0Ke0OzqIsDj0P5t0dl5aiAftShSgoRqp/0pyjS5bh+b9GrIzmfkrV2cn9LxxvSvA==, - } - dependencies: - jest-diff: 27.5.1 - mock-socket: 9.1.3 + /lilconfig@2.0.6: + resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} + engines: {node: '>=10'} dev: true - /jest-worker/27.5.1: - resolution: - { - integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==, - } - engines: { node: '>= 10.13.0' } - dependencies: - '@types/node': 17.0.41 - merge-stream: 2.0.0 - supports-color: 8.1.1 + /lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} dev: true - /jiti/1.16.0: - resolution: - { - integrity: sha512-L3BJStEf5NAqNuzrpfbN71dp43mYIcBUlCRea/vdyv5dW/AYa1d4bpelko4SHdY3I6eN9Wzyasxirj1/vv5kmg==, - } - hasBin: true + /lilconfig@3.0.0: + resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==} + engines: {node: '>=14'} dev: true - /js-tokens/4.0.0: - resolution: - { - integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, - } + /lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: true - /js-yaml/3.14.1: - resolution: - { - integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==, - } + /lint-staged@13.1.0: + resolution: {integrity: sha512-pn/sR8IrcF/T0vpWLilih8jmVouMlxqXxKuAojmbiGX5n/gDnz+abdPptlj0vYnbfE0SQNl3CY/HwtM0+yfOVQ==} + engines: {node: ^14.13.1 || >=16.0.0} hasBin: true dependencies: - argparse: 1.0.10 - esprima: 4.0.1 + cli-truncate: 3.1.0 + colorette: 2.0.19 + commander: 9.5.0 + debug: 4.3.4 + execa: 6.1.0 + lilconfig: 2.0.6 + listr2: 5.0.6 + micromatch: 4.0.5 + normalize-path: 3.0.0 + object-inspect: 1.12.2 + pidtree: 0.6.0 + string-argv: 0.3.1 + yaml: 2.2.1 + transitivePeerDependencies: + - enquirer + - supports-color dev: true - /js-yaml/4.1.0: - resolution: - { - integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==, - } - hasBin: true + /listr2@5.0.6: + resolution: {integrity: sha512-u60KxKBy1BR2uLJNTWNptzWQ1ob/gjMzIJPZffAENzpZqbMZ/5PrXXOomDcevIS/+IB7s1mmCEtSlT2qHWMqag==} + engines: {node: ^14.13.1 || >=16.0.0} + peerDependencies: + enquirer: '>= 2.3.0 < 3' + peerDependenciesMeta: + enquirer: + optional: true dependencies: - argparse: 2.0.1 + cli-truncate: 2.1.0 + colorette: 2.0.19 + log-update: 4.0.0 + p-map: 4.0.0 + rfdc: 1.3.0 + rxjs: 7.8.1 + through: 2.3.8 + wrap-ansi: 7.0.0 dev: true - /jsbn/0.1.1: - resolution: - { - integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==, - } + /loader-runner@4.3.0: + resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} + engines: {node: '>=6.11.5'} dev: true - /jsdom/20.0.0: - resolution: - { - integrity: sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==, - } - engines: { node: '>=14' } - peerDependencies: - canvas: ^2.5.0 - peerDependenciesMeta: - canvas: - optional: true + /loader-utils@2.0.4: + resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==} + engines: {node: '>=8.9.0'} dependencies: - abab: 2.0.6 - acorn: 8.7.1 - acorn-globals: 6.0.0 - cssom: 0.5.0 - cssstyle: 2.3.0 - data-urls: 3.0.2 - decimal.js: 10.3.1 - domexception: 4.0.0 - escodegen: 2.0.0 - form-data: 4.0.0 - html-encoding-sniffer: 3.0.0 - http-proxy-agent: 5.0.0 - https-proxy-agent: 5.0.1 - is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.1 - parse5: 7.0.0 - saxes: 6.0.0 - symbol-tree: 3.2.4 - tough-cookie: 4.0.0 - w3c-hr-time: 1.0.2 - w3c-xmlserializer: 3.0.0 - webidl-conversions: 7.0.0 - whatwg-encoding: 2.0.0 - whatwg-mimetype: 3.0.0 - whatwg-url: 11.0.0 - ws: 8.8.0 - xml-name-validator: 4.0.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate + big.js: 5.2.2 + emojis-list: 3.0.0 + json5: 2.2.3 + dev: true + + /locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + + /locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + dependencies: + p-locate: 4.1.0 dev: true - /jsesc/2.5.2: - resolution: - { - integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==, - } - engines: { node: '>=4' } - hasBin: true + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 dev: true - /json-beautify/1.1.1: - resolution: - { - integrity: sha512-17j+Hk2lado0xqKtUcyAjK0AtoHnPSIgktWRsEXgdFQFG9UnaGw6CHa0J7xsvulxRpFl6CrkDFHght1p5ZJc4A==, - } - hasBin: true - dev: false + /lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + dev: true - /json-bigint/1.0.0: - resolution: - { - integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==, - } - dependencies: - bignumber.js: 9.1.0 + /lodash.flattendeep@4.4.0: + resolution: {integrity: sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==} + dev: true + + /lodash.groupby@4.6.0: + resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} dev: false - /json-parse-even-better-errors/2.3.1: - resolution: - { - integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==, - } + /lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + dev: true + + /lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true - /json-schema-traverse/0.4.1: - resolution: - { - integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, - } + /lodash.truncate@4.4.2: + resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} dev: true - /json-schema-traverse/1.0.0: - resolution: - { - integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==, - } + /lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} dev: true - /json-schema/0.4.0: - resolution: - { - integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==, - } + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: true - /json-stable-stringify-without-jsonify/1.0.1: - resolution: - { - integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, - } + /log-update@4.0.0: + resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} + engines: {node: '>=10'} + dependencies: + ansi-escapes: 4.3.2 + cli-cursor: 3.1.0 + slice-ansi: 4.0.0 + wrap-ansi: 6.2.0 dev: true - /json-stringify-safe/5.0.1: - resolution: - { - integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==, - } + /long@5.2.3: + resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} dev: true - /json5/2.2.1: - resolution: - { - integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==, - } - engines: { node: '>=6' } + /longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + /loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + dependencies: + js-tokens: 4.0.0 dev: true - /jsonc-parser/3.2.0: - resolution: - { - integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==, - } + /loupe@3.1.3: + resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} dev: true - /jsonfile/6.1.0: - resolution: - { - integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==, - } + /lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: - universalify: 2.0.0 - optionalDependencies: - graceful-fs: 4.2.10 + tslib: 2.5.0 + + /lru-cache@10.0.0: + resolution: {integrity: sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw==} + engines: {node: 14 || >=16.14} dev: true - /jsprim/2.0.2: - resolution: - { - integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==, - } - engines: { '0': node >=0.6.0 } + /lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: - assert-plus: 1.0.0 - extsprintf: 1.3.0 - json-schema: 0.4.0 - verror: 1.10.0 + yallist: 3.1.1 dev: true - /just-debounce/1.1.0: - resolution: - { - integrity: sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==, - } - dev: false + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + dev: true + + /lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + dev: true + + /magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 - /kind-of/6.0.3: - resolution: - { - integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==, - } - engines: { node: '>=0.10.0' } + /make-dir@3.1.0: + resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} + engines: {node: '>=8'} + dependencies: + semver: 6.3.1 dev: true - /kleur/4.1.5: - resolution: - { - integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==, - } - engines: { node: '>=6' } + /make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + dependencies: + semver: 7.6.3 dev: true - /known-css-properties/0.25.0: - resolution: - { - integrity: sha512-b0/9J1O9Jcyik1GC6KC42hJ41jKwdO/Mq8Mdo5sYN+IuRTXs2YFHZC3kZSx6ueusqa95x3wLYe/ytKjbAfGixA==, - } + /makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + dependencies: + tmpl: 1.0.5 dev: true - /kolorist/1.6.0: - resolution: - { - integrity: sha512-dLkz37Ab97HWMx9KTes3Tbi3D1ln9fCAy2zr2YVExJasDRPGRaKcoE4fycWNtnCAJfjFqe0cnY+f8KT2JePEXQ==, - } + /map-obj@1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} dev: true - /lazy-ass/1.6.0: - resolution: - { - integrity: sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==, - } - engines: { node: '> 0.8' } + /map-obj@4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} dev: true - /levn/0.3.0: - resolution: - { - integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==, - } - engines: { node: '>= 0.8.0' } - dependencies: - prelude-ls: 1.1.2 - type-check: 0.3.2 + /map-or-similar@1.5.0: + resolution: {integrity: sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==} dev: true - /levn/0.4.1: - resolution: - { - integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, - } - engines: { node: '>= 0.8.0' } - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 + /map-stream@0.1.0: + resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} dev: true - /lilconfig/2.0.5: - resolution: - { - integrity: sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==, - } - engines: { node: '>=10' } + /markdown-table@3.0.3: + resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} dev: true - /lines-and-columns/1.2.4: - resolution: - { - integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==, - } + /mathml-tag-names@2.1.3: + resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} dev: true - /linkify-it/3.0.3: - resolution: - { - integrity: sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==, - } + /mdast-util-find-and-replace@2.2.2: + resolution: {integrity: sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==} dependencies: - uc.micro: 1.0.6 + '@types/mdast': 3.0.12 + escape-string-regexp: 5.0.0 + unist-util-is: 5.2.1 + unist-util-visit-parents: 5.1.3 dev: true - /lint-staged/13.0.3: - resolution: - { - integrity: sha512-9hmrwSCFroTSYLjflGI8Uk+GWAwMB4OlpU4bMJEAT5d/llQwtYKoim4bLOyLCuWFAhWEupE0vkIFqtw/WIsPug==, - } - engines: { node: ^14.13.1 || >=16.0.0 } - hasBin: true + /mdast-util-from-markdown@1.3.1: + resolution: {integrity: sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==} dependencies: - cli-truncate: 3.1.0 - colorette: 2.0.19 - commander: 9.4.0 - debug: 4.3.4 - execa: 6.1.0 - lilconfig: 2.0.5 - listr2: 4.0.5 - micromatch: 4.0.5 - normalize-path: 3.0.0 - object-inspect: 1.12.2 - pidtree: 0.6.0 - string-argv: 0.3.1 - yaml: 2.1.1 + '@types/mdast': 3.0.12 + '@types/unist': 2.0.8 + decode-named-character-reference: 1.0.2 + mdast-util-to-string: 3.2.0 + micromark: 3.2.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-decode-string: 1.1.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + unist-util-stringify-position: 3.0.3 + uvu: 0.5.6 transitivePeerDependencies: - - enquirer - supports-color dev: true - /listr2/3.14.0_enquirer@2.3.6: - resolution: - { - integrity: sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==, - } - engines: { node: '>=10.0.0' } - peerDependencies: - enquirer: '>= 2.3.0 < 3' - peerDependenciesMeta: - enquirer: - optional: true + /mdast-util-from-markdown@2.0.1: + resolution: {integrity: sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==} + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.0 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-decode-string: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /mdast-util-gfm-autolink-literal@1.0.3: + resolution: {integrity: sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==} dependencies: - cli-truncate: 2.1.0 - colorette: 2.0.19 - enquirer: 2.3.6 - log-update: 4.0.0 - p-map: 4.0.0 - rfdc: 1.3.0 - rxjs: 7.5.5 - through: 2.3.8 - wrap-ansi: 7.0.0 + '@types/mdast': 3.0.12 + ccount: 2.0.1 + mdast-util-find-and-replace: 2.2.2 + micromark-util-character: 1.2.0 dev: true - /listr2/4.0.5: - resolution: - { - integrity: sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==, - } - engines: { node: '>=12' } - peerDependencies: - enquirer: '>= 2.3.0 < 3' - peerDependenciesMeta: - enquirer: - optional: true + /mdast-util-gfm-footnote@1.0.2: + resolution: {integrity: sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==} dependencies: - cli-truncate: 2.1.0 - colorette: 2.0.19 - log-update: 4.0.0 - p-map: 4.0.0 - rfdc: 1.3.0 - rxjs: 7.5.5 - through: 2.3.8 - wrap-ansi: 7.0.0 + '@types/mdast': 3.0.12 + mdast-util-to-markdown: 1.5.0 + micromark-util-normalize-identifier: 1.1.0 dev: true - /loader-runner/4.3.0: - resolution: - { - integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==, - } - engines: { node: '>=6.11.5' } + /mdast-util-gfm-strikethrough@1.0.3: + resolution: {integrity: sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==} + dependencies: + '@types/mdast': 3.0.12 + mdast-util-to-markdown: 1.5.0 dev: true - /loader-utils/2.0.2: - resolution: - { - integrity: sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==, - } - engines: { node: '>=8.9.0' } + /mdast-util-gfm-table@1.0.7: + resolution: {integrity: sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==} dependencies: - big.js: 5.2.2 - emojis-list: 3.0.0 - json5: 2.2.1 + '@types/mdast': 3.0.12 + markdown-table: 3.0.3 + mdast-util-from-markdown: 1.3.1 + mdast-util-to-markdown: 1.5.0 + transitivePeerDependencies: + - supports-color dev: true - /local-pkg/0.4.1: - resolution: - { - integrity: sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==, - } - engines: { node: '>=14' } + /mdast-util-gfm-task-list-item@1.0.2: + resolution: {integrity: sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==} + dependencies: + '@types/mdast': 3.0.12 + mdast-util-to-markdown: 1.5.0 dev: true - /locate-path/5.0.0: - resolution: - { - integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==, - } - engines: { node: '>=8' } + /mdast-util-gfm@2.0.2: + resolution: {integrity: sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==} dependencies: - p-locate: 4.1.0 + mdast-util-from-markdown: 1.3.1 + mdast-util-gfm-autolink-literal: 1.0.3 + mdast-util-gfm-footnote: 1.0.2 + mdast-util-gfm-strikethrough: 1.0.3 + mdast-util-gfm-table: 1.0.7 + mdast-util-gfm-task-list-item: 1.0.2 + mdast-util-to-markdown: 1.5.0 + transitivePeerDependencies: + - supports-color dev: true - /locate-path/6.0.0: - resolution: - { - integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==, - } - engines: { node: '>=10' } + /mdast-util-phrasing@3.0.1: + resolution: {integrity: sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==} dependencies: - p-locate: 5.0.0 - dev: true + '@types/mdast': 3.0.12 + unist-util-is: 5.2.1 - /lodash.camelcase/4.3.0: - resolution: - { - integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==, - } - dev: true + /mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.2.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.0 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.1 + dev: false + + /mdast-util-to-markdown@1.5.0: + resolution: {integrity: sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==} + dependencies: + '@types/mdast': 3.0.12 + '@types/unist': 2.0.8 + longest-streak: 3.1.0 + mdast-util-phrasing: 3.0.1 + mdast-util-to-string: 3.2.0 + micromark-util-decode-string: 1.1.0 + unist-util-visit: 4.1.2 + zwitch: 2.0.4 + + /mdast-util-to-string@3.2.0: + resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==} + dependencies: + '@types/mdast': 3.0.12 + + /mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + dependencies: + '@types/mdast': 4.0.4 + dev: false - /lodash.memoize/4.1.2: - resolution: - { - integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==, - } + /mdast-util-toc@6.1.1: + resolution: {integrity: sha512-Er21728Kow8hehecK2GZtb7Ny3omcoPUVrmObiSUwmoRYVZaXLR751QROEFjR8W/vAQdHMLj49Lz20J55XaNpw==} + dependencies: + '@types/extend': 3.0.1 + '@types/mdast': 3.0.12 + extend: 3.0.2 + github-slugger: 2.0.0 + mdast-util-to-string: 3.2.0 + unist-util-is: 5.2.1 + unist-util-visit: 4.1.2 dev: true - /lodash.merge/4.6.2: - resolution: - { - integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, - } + /mdn-data@2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} dev: true - /lodash.once/4.1.1: - resolution: - { - integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==, - } + /mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} dev: true - /lodash.truncate/4.4.2: - resolution: - { - integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==, - } + /media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} dev: true - /lodash.uniq/4.5.0: - resolution: - { - integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==, - } + /memfs@4.7.7: + resolution: {integrity: sha512-x9qc6k88J/VVwnfTkJV8pRRswJ2156Rc4w5rciRqKceFDZ0y1MqsNL9pkg5sE0GOcDzZYbonreALhaHzg1siFw==} + engines: {node: '>= 4.0.0'} + dependencies: + tslib: 2.5.0 dev: true - /lodash/4.17.21: - resolution: - { - integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, - } + /memoize-weak@1.0.2: + resolution: {integrity: sha512-gj39xkrjEw7nCn4nJ1M5ms6+MyMlyiGmttzsqAUsAKn6bYKwuTHh/AO3cKPF8IBrTIYTxb0wWXFs3E//Y8VoWQ==} + dev: false + + /memoizerific@1.11.3: + resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==} + dependencies: + map-or-similar: 1.5.0 dev: true - /log-symbols/4.1.0: - resolution: - { - integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==, - } - engines: { node: '>=10' } + /meow@10.1.5: + resolution: {integrity: sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 + '@types/minimist': 1.2.2 + camelcase-keys: 7.0.2 + decamelize: 5.0.1 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 8.0.0 + redent: 4.0.0 + trim-newlines: 4.1.1 + type-fest: 1.4.0 + yargs-parser: 20.2.9 dev: true - /log-update/4.0.0: - resolution: - { - integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==, - } - engines: { node: '>=10' } + /merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + dev: true + + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true + + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + dev: true + + /micromark-core-commonmark@1.1.0: + resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==} + dependencies: + decode-named-character-reference: 1.0.2 + micromark-factory-destination: 1.1.0 + micromark-factory-label: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-factory-title: 1.1.0 + micromark-factory-whitespace: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-chunked: 1.1.0 + micromark-util-classify-character: 1.1.0 + micromark-util-html-tag-name: 1.2.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-subtokenize: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + dev: true + + /micromark-core-commonmark@2.0.1: + resolution: {integrity: sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==} + dependencies: + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-factory-destination: 2.0.0 + micromark-factory-label: 2.0.0 + micromark-factory-space: 2.0.0 + micromark-factory-title: 2.0.0 + micromark-factory-whitespace: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-classify-character: 2.0.0 + micromark-util-html-tag-name: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-subtokenize: 2.0.1 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-extension-gfm-autolink-literal@1.0.5: + resolution: {integrity: sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==} dependencies: - ansi-escapes: 4.3.2 - cli-cursor: 3.1.0 - slice-ansi: 4.0.0 - wrap-ansi: 6.2.0 + micromark-util-character: 1.2.0 + micromark-util-sanitize-uri: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 dev: true - /long/4.0.0: - resolution: - { - integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==, - } + /micromark-extension-gfm-footnote@1.1.2: + resolution: {integrity: sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==} + dependencies: + micromark-core-commonmark: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-sanitize-uri: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 dev: true - /long/5.2.0: - resolution: - { - integrity: sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==, - } + /micromark-extension-gfm-strikethrough@1.0.7: + resolution: {integrity: sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==} + dependencies: + micromark-util-chunked: 1.1.0 + micromark-util-classify-character: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 dev: true - /loupe/2.3.4: - resolution: - { - integrity: sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==, - } + /micromark-extension-gfm-table@1.0.7: + resolution: {integrity: sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==} dependencies: - get-func-name: 2.0.0 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 dev: true - /lower-case/2.0.2: - resolution: - { - integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==, - } + /micromark-extension-gfm-tagfilter@1.0.2: + resolution: {integrity: sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==} dependencies: - tslib: 2.4.0 + micromark-util-types: 1.1.0 dev: true - /lru-cache/6.0.0: - resolution: - { - integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==, - } - engines: { node: '>=10' } + /micromark-extension-gfm-task-list-item@1.0.5: + resolution: {integrity: sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==} dependencies: - yallist: 4.0.0 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 dev: true - /magic-string/0.25.9: - resolution: - { - integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==, - } + /micromark-extension-gfm@2.0.3: + resolution: {integrity: sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==} dependencies: - sourcemap-codec: 1.4.8 + micromark-extension-gfm-autolink-literal: 1.0.5 + micromark-extension-gfm-footnote: 1.1.2 + micromark-extension-gfm-strikethrough: 1.0.7 + micromark-extension-gfm-table: 1.0.7 + micromark-extension-gfm-tagfilter: 1.0.2 + micromark-extension-gfm-task-list-item: 1.0.5 + micromark-util-combine-extensions: 1.1.0 + micromark-util-types: 1.1.0 dev: true - /magic-string/0.26.3: - resolution: - { - integrity: sha512-u1Po0NDyFcwdg2nzHT88wSK0+Rih0N1M+Ph1Sp08k8yvFFU3KR72wryS7e1qMPJypt99WB7fIFVCA92mQrMjrg==, - } - engines: { node: '>=12' } + /micromark-factory-destination@1.1.0: + resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==} dependencies: - sourcemap-codec: 1.4.8 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 dev: true - /make-dir/3.1.0: - resolution: - { - integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==, - } - engines: { node: '>=8' } + /micromark-factory-destination@2.0.0: + resolution: {integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==} dependencies: - semver: 6.3.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-factory-label@1.1.0: + resolution: {integrity: sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==} + dependencies: + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 dev: true - /make-error/1.3.6: - resolution: - { - integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==, - } + /micromark-factory-label@2.0.0: + resolution: {integrity: sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==} + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-factory-space@1.1.0: + resolution: {integrity: sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==} + dependencies: + micromark-util-character: 1.2.0 + micromark-util-types: 1.1.0 dev: true - /map-obj/1.0.1: - resolution: - { - integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==, - } - engines: { node: '>=0.10.0' } + /micromark-factory-space@2.0.0: + resolution: {integrity: sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==} + dependencies: + micromark-util-character: 2.1.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-factory-title@1.1.0: + resolution: {integrity: sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==} + dependencies: + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 dev: true - /map-obj/4.3.0: - resolution: - { - integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==, - } - engines: { node: '>=8' } + /micromark-factory-title@2.0.0: + resolution: {integrity: sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==} + dependencies: + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-factory-whitespace@1.1.0: + resolution: {integrity: sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==} + dependencies: + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 dev: true - /markdown-it-anchor/8.6.5_2zb4u3vubltivolgu556vv4aom: - resolution: - { - integrity: sha512-PI1qEHHkTNWT+X6Ip9w+paonfIQ+QZP9sCeMYi47oqhH+EsW8CrJ8J7CzV19QVOj6il8ATGbK2nTECj22ZHGvQ==, - } - peerDependencies: - '@types/markdown-it': '*' - markdown-it: '*' + /micromark-factory-whitespace@2.0.0: + resolution: {integrity: sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==} + dependencies: + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-character@1.2.0: + resolution: {integrity: sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==} dependencies: - '@types/markdown-it': 12.2.3 - markdown-it: 12.3.2 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + + /micromark-util-character@2.1.0: + resolution: {integrity: sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==} + dependencies: + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-chunked@1.1.0: + resolution: {integrity: sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==} + dependencies: + micromark-util-symbol: 1.1.0 dev: true - /markdown-it-attrs/4.1.4_markdown-it@12.3.2: - resolution: - { - integrity: sha512-53Zfv8PTb6rlVFDlD106xcZHKBSsRZKJ2IW/rTxEJBEVbVaoxaNsmRkG0HXfbHl2SK8kaxZ2QKqdthWy/QBwmA==, - } - engines: { node: '>=6' } - peerDependencies: - markdown-it: '>= 9.0.0' + /micromark-util-chunked@2.0.0: + resolution: {integrity: sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==} + dependencies: + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-classify-character@1.1.0: + resolution: {integrity: sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==} dependencies: - markdown-it: 12.3.2 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 dev: true - /markdown-it-emoji/2.0.2: - resolution: - { - integrity: sha512-zLftSaNrKuYl0kR5zm4gxXjHaOI3FAOEaloKmRA5hijmJZvSjmxcokOLlzycb/HXlUFWzXqpIEoyEMCE4i9MvQ==, - } + /micromark-util-classify-character@2.0.0: + resolution: {integrity: sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==} + dependencies: + micromark-util-character: 2.1.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-combine-extensions@1.1.0: + resolution: {integrity: sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==} + dependencies: + micromark-util-chunked: 1.1.0 + micromark-util-types: 1.1.0 dev: true - /markdown-it/12.3.2: - resolution: - { - integrity: sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==, - } - hasBin: true + /micromark-util-combine-extensions@2.0.0: + resolution: {integrity: sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==} dependencies: - argparse: 2.0.1 - entities: 2.1.0 - linkify-it: 3.0.3 - mdurl: 1.0.1 - uc.micro: 1.0.6 + micromark-util-chunked: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-decode-numeric-character-reference@1.1.0: + resolution: {integrity: sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==} + dependencies: + micromark-util-symbol: 1.1.0 + + /micromark-util-decode-numeric-character-reference@2.0.1: + resolution: {integrity: sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==} + dependencies: + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-decode-string@1.1.0: + resolution: {integrity: sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==} + dependencies: + decode-named-character-reference: 1.0.2 + micromark-util-character: 1.2.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-symbol: 1.1.0 + + /micromark-util-decode-string@2.0.0: + resolution: {integrity: sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==} + dependencies: + decode-named-character-reference: 1.0.2 + micromark-util-character: 2.1.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-encode@1.1.0: + resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==} dev: true - /mathml-tag-names/2.1.3: - resolution: - { - integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==, - } + /micromark-util-encode@2.0.0: + resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==} + dev: false + + /micromark-util-html-tag-name@1.2.0: + resolution: {integrity: sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==} dev: true - /mdn-data/2.0.14: - resolution: - { - integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==, - } + /micromark-util-html-tag-name@2.0.0: + resolution: {integrity: sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==} + dev: false + + /micromark-util-normalize-identifier@1.1.0: + resolution: {integrity: sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==} + dependencies: + micromark-util-symbol: 1.1.0 dev: true - /mdurl/1.0.1: - resolution: - { - integrity: sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==, - } + /micromark-util-normalize-identifier@2.0.0: + resolution: {integrity: sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==} + dependencies: + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-resolve-all@1.1.0: + resolution: {integrity: sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==} + dependencies: + micromark-util-types: 1.1.0 dev: true - /meow/9.0.0: - resolution: - { - integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==, - } - engines: { node: '>=10' } + /micromark-util-resolve-all@2.0.0: + resolution: {integrity: sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==} dependencies: - '@types/minimist': 1.2.2 - camelcase-keys: 6.2.2 - decamelize: 1.2.0 - decamelize-keys: 1.1.0 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 3.0.3 - read-pkg-up: 7.0.1 - redent: 3.0.0 - trim-newlines: 3.0.1 - type-fest: 0.18.1 - yargs-parser: 20.2.9 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-sanitize-uri@1.2.0: + resolution: {integrity: sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==} + dependencies: + micromark-util-character: 1.2.0 + micromark-util-encode: 1.1.0 + micromark-util-symbol: 1.1.0 dev: true - /merge-stream/2.0.0: - resolution: - { - integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==, - } + /micromark-util-sanitize-uri@2.0.0: + resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==} + dependencies: + micromark-util-character: 2.1.0 + micromark-util-encode: 2.0.0 + micromark-util-symbol: 2.0.0 + dev: false + + /micromark-util-subtokenize@1.1.0: + resolution: {integrity: sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==} + dependencies: + micromark-util-chunked: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 dev: true - /merge2/1.4.1: - resolution: - { - integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==, - } - engines: { node: '>= 8' } + /micromark-util-subtokenize@2.0.1: + resolution: {integrity: sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==} + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + dev: false + + /micromark-util-symbol@1.1.0: + resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==} + + /micromark-util-symbol@2.0.0: + resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==} + dev: false + + /micromark-util-types@1.1.0: + resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==} + + /micromark-util-types@2.0.0: + resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==} + dev: false + + /micromark@3.2.0: + resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} + dependencies: + '@types/debug': 4.1.8 + debug: 4.4.0 + decode-named-character-reference: 1.0.2 + micromark-core-commonmark: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-chunked: 1.1.0 + micromark-util-combine-extensions: 1.1.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-encode: 1.1.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-sanitize-uri: 1.2.0 + micromark-util-subtokenize: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + transitivePeerDependencies: + - supports-color dev: true - /micromatch/4.0.5: - resolution: - { - integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==, - } - engines: { node: '>=8.6' } + /micromark@4.0.0: + resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} + dependencies: + '@types/debug': 4.1.8 + debug: 4.4.0 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.1 + micromark-factory-space: 2.0.0 + micromark-util-character: 2.1.0 + micromark-util-chunked: 2.0.0 + micromark-util-combine-extensions: 2.0.0 + micromark-util-decode-numeric-character-reference: 2.0.1 + micromark-util-encode: 2.0.0 + micromark-util-normalize-identifier: 2.0.0 + micromark-util-resolve-all: 2.0.0 + micromark-util-sanitize-uri: 2.0.0 + micromark-util-subtokenize: 2.0.1 + micromark-util-symbol: 2.0.0 + micromark-util-types: 2.0.0 + transitivePeerDependencies: + - supports-color + dev: false + + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} dependencies: - braces: 3.0.2 + braces: 3.0.3 picomatch: 2.3.1 dev: true - /mime-db/1.52.0: - resolution: - { - integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==, - } - engines: { node: '>= 0.6' } + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} dev: true - /mime-types/2.1.35: - resolution: - { - integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==, - } - engines: { node: '>= 0.6' } + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 dev: true - /mimic-fn/2.1.0: - resolution: - { - integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==, - } - engines: { node: '>=6' } + /mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} dev: true - /mimic-fn/4.0.0: - resolution: - { - integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==, - } - engines: { node: '>=12' } + /mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} dev: true - /min-indent/1.0.1: - resolution: - { - integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==, - } - engines: { node: '>=4' } + /min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} dev: true - /minimatch/3.1.2: - resolution: - { - integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, - } + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.11 dev: true - /minimist-options/4.1.0: - resolution: - { - integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==, - } - engines: { node: '>= 6' } + /minimatch@8.0.4: + resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimist-options@4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} dependencies: arrify: 1.0.1 is-plain-obj: 1.1.0 kind-of: 6.0.3 dev: true - /minimist/1.2.6: - resolution: - { - integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==, - } + /minimist@1.2.7: + resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} + dev: true + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} dev: true - /minipass/3.3.4: - resolution: - { - integrity: sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==, - } - engines: { node: '>=8' } + /minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} dependencies: yallist: 4.0.0 dev: true - /minizlib/2.1.2: - resolution: - { - integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==, - } - engines: { node: '>= 8' } + /minipass@4.2.8: + resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} + engines: {node: '>=8'} + dev: true + + /minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + dev: true + + /minipass@7.0.2: + resolution: {integrity: sha512-eL79dXrE1q9dBbDCLg7xfn/vl7MS4F1gvJAgjJrQli/jbQWdUttuVawphqpffoIYfRdq78LHx6GP4bU/EQ2ATA==} + engines: {node: '>=16 || 14 >=14.17'} + dev: true + + /minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} dependencies: - minipass: 3.3.4 + minipass: 3.3.6 yallist: 4.0.0 dev: true - /mkdirp/0.5.6: - resolution: - { - integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==, - } + /mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true dependencies: - minimist: 1.2.6 + minimist: 1.2.8 dev: true - /mkdirp/1.0.4: - resolution: - { - integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==, - } - engines: { node: '>=10' } + /mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} hasBin: true dev: true - /mlly/0.5.14: - resolution: - { - integrity: sha512-DgRgNUSX9NIxxCxygX4Xeg9C7GX7OUx1wuQ8cXx9o9LE0e9wrH+OZ9fcnrlEedsC/rtqry3ZhUddC759XD/L0w==, - } - dependencies: - acorn: 8.8.0 - pathe: 0.3.7 - pkg-types: 0.3.5 - ufo: 0.8.5 + /mkdirp@2.1.3: + resolution: {integrity: sha512-sjAkg21peAG9HS+Dkx7hlG9Ztx7HLeKnvB3NQRcu/mltCVmvkF0pisbiTSfDVYTT86XEfZrTUosLdZLStquZUw==} + engines: {node: '>=10'} + hasBin: true dev: true - /mock-socket/9.1.3: - resolution: - { - integrity: sha512-uz8lx8c5wuJYJ21f5UtovqpV0+KJuVwE7cVOLNhrl2QW/CvmstOLRfjXnLSbfFHZtJtiaSGQu0oCJA8SmRcK6A==, - } - engines: { node: '>= 8' } + /monaco-editor@0.50.0: + resolution: {integrity: sha512-8CclLCmrRRh+sul7C08BmPBP3P8wVWfBHomsTcndxg5NRCEPfu/mc2AGU8k37ajjDVXcXFc12ORAMUkmk+lkFA==} + dev: false + + /mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + /mrmime@2.0.0: + resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} + engines: {node: '>=10'} + + /ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} dev: true - /mri/1.2.0: - resolution: - { - integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==, - } - engines: { node: '>=4' } + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} dev: true - /mrmime/1.0.1: - resolution: - { - integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==, - } - engines: { node: '>=10' } + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + /ms@3.0.0-canary.1: + resolution: {integrity: sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==} + engines: {node: '>=12.13'} dev: true - /ms/2.0.0: - resolution: - { - integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==, - } + /mustache@4.2.0: + resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} + hasBin: true dev: true - /ms/2.1.2: - resolution: - { - integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==, - } + /mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 dev: true - /nanoid/3.3.4: - resolution: - { - integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==, - } - engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + /nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - /natural-compare/1.4.0: - resolution: - { - integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, - } + /nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true + + /negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} dev: true - /neo-async/2.6.2: - resolution: - { - integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==, - } + /neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: true - /no-case/3.0.4: - resolution: - { - integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==, - } + /no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: lower-case: 2.0.2 - tslib: 2.4.0 + tslib: 2.5.0 + + /node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} dev: true - /node-fetch/2.6.7: - resolution: - { - integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==, - } - engines: { node: 4.x || >=6.0.0 } + /node-fetch@2.6.8: + resolution: {integrity: sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==} + engines: {node: 4.x || >=6.0.0} peerDependencies: encoding: ^0.1.0 peerDependenciesMeta: @@ -7367,113 +10296,100 @@ packages: whatwg-url: 5.0.0 dev: true - /node-gyp-build/4.5.0: - resolution: - { - integrity: sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==, - } - hasBin: true + /node-fetch@3.2.10: + resolution: {integrity: sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 dev: true - /node-releases/2.0.4: - resolution: - { - integrity: sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==, - } + /node-fetch@3.3.0: + resolution: {integrity: sha512-BKwRP/O0UvoMKp7GNdwPlObhYGB5DQqwhEDQlNKuoqwVYSxkSZCSbHjnFFmUEtwSKRPU4kNK8PbDYYitwaE3QA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 dev: true - /nopt/5.0.0: - resolution: - { - integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==, - } - engines: { node: '>=6' } + /node-gyp-build@4.6.0: + resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} hasBin: true + dev: true + + /node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + dev: true + + /node-preload@0.2.1: + resolution: {integrity: sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==} + engines: {node: '>=8'} dependencies: - abbrev: 1.1.1 + process-on-spawn: 1.0.0 + dev: true + + /node-releases@2.0.14: + resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} dev: true - /normalize-package-data/2.5.0: - resolution: - { - integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==, - } + /nopt@5.0.0: + resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} + engines: {node: '>=6'} + hasBin: true dependencies: - hosted-git-info: 2.8.9 - resolve: 1.22.1 - semver: 5.7.1 - validate-npm-package-license: 3.0.4 + abbrev: 1.1.1 dev: true - /normalize-package-data/3.0.3: - resolution: - { - integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==, - } - engines: { node: '>=10' } + /normalize-package-data@3.0.3: + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} + engines: {node: '>=10'} dependencies: hosted-git-info: 4.1.0 - is-core-module: 2.9.0 - semver: 7.3.7 + is-core-module: 2.13.0 + semver: 7.6.3 validate-npm-package-license: 3.0.4 dev: true - /normalize-path/3.0.0: - resolution: - { - integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==, - } - engines: { node: '>=0.10.0' } + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} dev: true - /normalize-range/0.1.2: - resolution: - { - integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==, - } - engines: { node: '>=0.10.0' } + /normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} dev: true - /normalize-selector/0.2.0: - resolution: - { - integrity: sha512-dxvWdI8gw6eAvk9BlPffgEoGfM7AdijoCwOEJge3e3ulT2XLgmU7KvvxprOaCu05Q1uGRHmOhHe1r6emZoKyFw==, - } + /normalize-url@6.1.0: + resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} + engines: {node: '>=10'} dev: true - /normalize-url/6.1.0: - resolution: - { - integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==, - } - engines: { node: '>=10' } - dev: true + /normalize-url@8.0.2: + resolution: {integrity: sha512-Ee/R3SyN4BuynXcnTaekmaVdbDAEiNrHqjQIA37mHU8G9pf7aaAD4ZX3XjBLo6rsdcxA/gtkcNYZLt30ACgynw==} + engines: {node: '>=14.16'} + requiresBuild: true + dev: false + optional: true - /npm-run-path/4.0.1: - resolution: - { - integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==, - } - engines: { node: '>=8' } + /npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} dependencies: path-key: 3.1.1 dev: true - /npm-run-path/5.1.0: - resolution: - { - integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==, - } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + /npm-run-path@5.1.0: + resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: path-key: 4.0.0 dev: true - /npmlog/5.0.1: - resolution: - { - integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==, - } + /npmlog@5.0.1: + resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} dependencies: are-we-there-yet: 2.0.0 console-control-strings: 1.1.0 @@ -7481,444 +10397,445 @@ packages: set-blocking: 2.0.0 dev: true - /nth-check/2.0.1: - resolution: - { - integrity: sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==, - } + /nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} dependencies: boolbase: 1.0.0 dev: true - /nwsapi/2.2.1: - resolution: - { - integrity: sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==, - } + /nwsapi@2.2.2: + resolution: {integrity: sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==} + dev: true + + /nyc@15.1.0: + resolution: {integrity: sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==} + engines: {node: '>=8.9'} + hasBin: true + dependencies: + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + caching-transform: 4.0.0 + convert-source-map: 1.9.0 + decamelize: 1.2.0 + find-cache-dir: 3.3.2 + find-up: 4.1.0 + foreground-child: 2.0.0 + get-package-type: 0.1.0 + glob: 7.2.3 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-hook: 3.0.0 + istanbul-lib-instrument: 4.0.3 + istanbul-lib-processinfo: 2.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.7 + make-dir: 3.1.0 + node-preload: 0.2.1 + p-map: 3.0.0 + process-on-spawn: 1.0.0 + resolve-from: 5.0.0 + rimraf: 3.0.2 + signal-exit: 3.0.7 + spawn-wrap: 2.0.0 + test-exclude: 6.0.0 + yargs: 15.4.1 + transitivePeerDependencies: + - supports-color dev: true - /object-assign/4.1.1: - resolution: - { - integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==, - } - engines: { node: '>=0.10.0' } + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} dev: true - /object-hash/1.3.1: - resolution: - { - integrity: sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==, - } - engines: { node: '>= 0.10.0' } + /object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} dev: true - /object-hash/3.0.0: - resolution: - { - integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==, - } - engines: { node: '>= 6' } + /object-inspect@1.12.2: + resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} dev: true - /object-inspect/1.12.2: - resolution: - { - integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==, - } + /object-inspect@1.13.2: + resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + engines: {node: '>= 0.4'} + dev: true - /object-keys/1.1.1: - resolution: - { - integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==, - } - engines: { node: '>= 0.4' } - dev: false + /object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + dev: true - /object.assign/4.1.2: - resolution: - { - integrity: sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==, - } - engines: { node: '>= 0.4' } + /object.assign@4.1.4: + resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 + call-bind: 1.0.7 + define-properties: 1.2.0 has-symbols: 1.0.3 object-keys: 1.1.1 - dev: false + dev: true + + /object.fromentries@2.0.6: + resolution: {integrity: sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.1.4 + es-abstract: 1.21.1 + dev: true - /on-finished/2.3.0: - resolution: - { - integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==, - } - engines: { node: '>= 0.8' } + /object.groupby@1.0.0: + resolution: {integrity: sha512-70MWG6NfRH9GnbZOikuhPPYzpUpof9iW2J9E4dW7FXTqPNb6rllE6u39SKwwiNh8lCwX3DDb5OgcKGiEBrTTyw==} + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.0 + es-abstract: 1.22.1 + get-intrinsic: 1.2.4 + dev: true + + /object.values@1.1.6: + resolution: {integrity: sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.1.4 + es-abstract: 1.21.1 + dev: true + + /on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} dependencies: ee-first: 1.1.1 dev: true - /once/1.4.0: - resolution: - { - integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==, - } + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: wrappy: 1.0.2 dev: true - /onetime/5.1.2: - resolution: - { - integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==, - } - engines: { node: '>=6' } + /onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} dependencies: mimic-fn: 2.1.0 dev: true - /onetime/6.0.0: - resolution: - { - integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==, - } - engines: { node: '>=12' } + /onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} dependencies: mimic-fn: 4.0.0 dev: true - /optionator/0.8.3: - resolution: - { - integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==, - } - engines: { node: '>= 0.8.0' } + /open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + dev: true + + /optionator@0.8.3: + resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==} + engines: {node: '>= 0.8.0'} dependencies: deep-is: 0.1.4 fast-levenshtein: 2.0.6 levn: 0.3.0 prelude-ls: 1.1.2 type-check: 0.3.2 - word-wrap: 1.2.3 + word-wrap: 1.2.4 dev: true - /optionator/0.9.1: - resolution: - { - integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==, - } - engines: { node: '>= 0.8.0' } + /optionator@0.9.3: + resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} + engines: {node: '>= 0.8.0'} dependencies: + '@aashutoshrathi/word-wrap': 1.2.6 deep-is: 0.1.4 fast-levenshtein: 2.0.6 levn: 0.4.1 prelude-ls: 1.2.1 type-check: 0.4.0 - word-wrap: 1.2.3 dev: true - /ospath/1.2.2: - resolution: - { - integrity: sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==, - } + /os-homedir@1.0.2: + resolution: {integrity: sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==} + engines: {node: '>=0.10.0'} dev: true - /p-limit/2.3.0: - resolution: - { - integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==, - } - engines: { node: '>=6' } + /p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} dependencies: p-try: 2.2.0 dev: true - /p-limit/3.1.0: - resolution: - { - integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==, - } - engines: { node: '>=10' } + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} dependencies: yocto-queue: 0.1.0 dev: true - /p-locate/4.1.0: - resolution: - { - integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==, - } - engines: { node: '>=8' } + /p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} dependencies: p-limit: 2.3.0 dev: true - /p-locate/5.0.0: - resolution: - { - integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==, - } - engines: { node: '>=10' } + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} dependencies: p-limit: 3.1.0 dev: true - /p-map/4.0.0: - resolution: - { - integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==, - } - engines: { node: '>=10' } + /p-map@3.0.0: + resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} + engines: {node: '>=8'} dependencies: aggregate-error: 3.1.0 dev: true - /p-try/2.2.0: - resolution: - { - integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==, - } - engines: { node: '>=6' } + /p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + dependencies: + aggregate-error: 3.1.0 dev: true - /param-case/3.0.4: - resolution: - { - integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==, - } - dependencies: - dot-case: 3.0.4 - tslib: 2.4.0 + /p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} dev: true - /parent-module/1.0.1: - resolution: - { - integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, - } - engines: { node: '>=6' } + /package-hash@4.0.0: + resolution: {integrity: sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==} + engines: {node: '>=8'} dependencies: - callsites: 3.1.0 + graceful-fs: 4.2.11 + hasha: 5.2.2 + lodash.flattendeep: 4.4.0 + release-zalgo: 1.0.0 dev: true - /parse-cache-control/1.0.1: - resolution: - { - integrity: sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==, - } + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 dev: true - /parse-json/5.2.0: - resolution: - { - integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==, - } - engines: { node: '>=8' } + /parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} dependencies: - '@babel/code-frame': 7.16.7 + '@babel/code-frame': 7.22.13 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 dev: true - /parse-srcset/1.0.2: - resolution: - { - integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==, - } - dev: false + /parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + dev: true - /parse5/7.0.0: - resolution: - { - integrity: sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==, - } - dependencies: - entities: 4.3.0 + /parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} dev: true - /parseurl/1.3.3: - resolution: - { - integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==, - } - engines: { node: '>= 0.8' } + /parse5@7.1.2: + resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + dependencies: + entities: 4.4.0 + + /parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} dev: true - /pascal-case/3.1.2: - resolution: - { - integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==, - } + /pascal-case@3.1.2: + resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} dependencies: no-case: 3.0.4 - tslib: 2.4.0 + tslib: 2.5.0 + + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} dev: true - /path-case/3.0.4: - resolution: - { - integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==, - } - dependencies: - dot-case: 3.0.4 - tslib: 2.4.0 + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} dev: true - /path-exists/4.0.0: - resolution: - { - integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, - } - engines: { node: '>=8' } + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} dev: true - /path-is-absolute/1.0.1: - resolution: - { - integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==, - } - engines: { node: '>=0.10.0' } + /path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} dev: true - /path-key/3.1.1: - resolution: - { - integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, - } - engines: { node: '>=8' } + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: true - /path-key/4.0.0: - resolution: - { - integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==, - } - engines: { node: '>=12' } + /path-scurry@1.10.1: + resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + lru-cache: 10.0.0 + minipass: 7.0.2 dev: true - /path-parse/1.0.7: - resolution: - { - integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, - } + /path-to-regexp@0.1.10: + resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} dev: true - /path-type/4.0.0: - resolution: - { - integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==, - } - engines: { node: '>=8' } + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} dev: true - /pathe/0.2.0: - resolution: - { - integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==, - } + /pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} dev: true - /pathe/0.3.7: - resolution: - { - integrity: sha512-yz7GK+kSsS27x727jtXpd5VT4dDfP/JDIQmaowfxyWCnFjOWtE1VIh7i6TzcSfzW0n4+bRQztj1VdKnITNq/MA==, - } + /pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} dev: true - /pathval/1.1.1: - resolution: - { - integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==, - } + /pause-stream@0.0.11: + resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} + dependencies: + through: 2.3.8 dev: true - /pend/1.2.0: - resolution: - { - integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==, - } + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true - /performance-now/2.1.0: - resolution: - { - integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==, - } + /picocolors@1.1.0: + resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} dev: true - /picocolors/1.0.0: - resolution: - { - integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==, - } + /picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true - /picomatch/2.3.1: - resolution: - { - integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==, - } - engines: { node: '>=8.6' } + /picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} dev: true - /pidtree/0.6.0: - resolution: - { - integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==, - } - engines: { node: '>=0.10' } + /pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} hasBin: true dev: true - /pify/2.3.0: - resolution: - { - integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==, - } - engines: { node: '>=0.10.0' } + /pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + dev: true + + /pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} dev: true - /pkg-dir/4.2.0: - resolution: - { - integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==, - } - engines: { node: '>=8' } + /pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} dependencies: find-up: 4.1.0 dev: true - /pkg-types/0.3.5: - resolution: - { - integrity: sha512-VkxCBFVgQhNHYk9subx+HOhZ4jzynH11ah63LZsprTKwPCWG9pfWBlkElWFbvkP9BVR0dP1jS9xPdhaHQNK74Q==, - } + /playwright-core@1.49.1: + resolution: {integrity: sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==} + engines: {node: '>=18'} + hasBin: true + dev: true + + /playwright-core@1.50.1: + resolution: {integrity: sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==} + engines: {node: '>=18'} + hasBin: true + dev: true + + /playwright-core@1.52.0: + resolution: {integrity: sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==} + engines: {node: '>=18'} + hasBin: true + dev: true + + /playwright@1.49.1: + resolution: {integrity: sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==} + engines: {node: '>=18'} + hasBin: true dependencies: - jsonc-parser: 3.2.0 - mlly: 0.5.14 - pathe: 0.3.7 + playwright-core: 1.49.1 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /playwright@1.50.1: + resolution: {integrity: sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==} + engines: {node: '>=18'} + hasBin: true + dependencies: + playwright-core: 1.50.1 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /playwright@1.52.0: + resolution: {integrity: sha512-JAwMNMBlxJ2oD1kce4KPtMkDeKGHQstdpFPcPH3maElAXon/QZeTvtsfXmTMRyO9TslfoYOXkSsvao2nE1ilTw==} + engines: {node: '>=18'} + hasBin: true + dependencies: + playwright-core: 1.52.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /polished@4.2.2: + resolution: {integrity: sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ==} + engines: {node: '>=10'} + dependencies: + '@babel/runtime': 7.23.2 dev: true - /postcss-calc/8.2.4_postcss@8.4.14: - resolution: - { - integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==, - } + /postcss-calc@8.2.4(postcss@8.4.31): + resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} peerDependencies: postcss: ^8.2.2 dependencies: - postcss: 8.4.14 - postcss-selector-parser: 6.0.10 + postcss: 8.4.31 + postcss-selector-parser: 6.0.13 postcss-value-parser: 4.2.0 dev: true - /postcss-cli/9.1.0_gimuzhxoryaoe5ripiipjtmpcu: - resolution: - { - integrity: sha512-zvDN2ADbWfza42sAnj+O2uUWyL0eRL1V+6giM2vi4SqTR3gTYy8XzcpfwccayF2szcUif0HMmXiEaDv9iEhcpw==, - } - engines: { node: '>=12' } + /postcss-cli@9.1.0(postcss@8.4.31): + resolution: {integrity: sha512-zvDN2ADbWfza42sAnj+O2uUWyL0eRL1V+6giM2vi4SqTR3gTYy8XzcpfwccayF2szcUif0HMmXiEaDv9iEhcpw==} + engines: {node: '>=12'} hasBin: true peerDependencies: postcss: ^8.0.0 @@ -7929,144 +10846,124 @@ packages: get-stdin: 9.0.0 globby: 12.2.0 picocolors: 1.0.0 - postcss: 8.4.14 - postcss-load-config: 3.1.4_gimuzhxoryaoe5ripiipjtmpcu - postcss-reporter: 7.0.5_postcss@8.4.14 + postcss: 8.4.31 + postcss-load-config: 3.1.4(postcss@8.4.31) + postcss-reporter: 7.0.5(postcss@8.4.31) pretty-hrtime: 1.0.3 read-cache: 1.0.0 slash: 4.0.0 - yargs: 17.5.1 + yargs: 17.7.2 transitivePeerDependencies: - ts-node dev: true - /postcss-colormin/5.3.0_postcss@8.4.14: - resolution: - { - integrity: sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-colormin@5.3.0(postcss@8.4.31): + resolution: {integrity: sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - browserslist: 4.20.3 + browserslist: 4.23.0 caniuse-api: 3.0.0 - colord: 2.9.2 - postcss: 8.4.14 + colord: 2.9.3 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true - /postcss-convert-values/5.1.1_postcss@8.4.14: - resolution: - { - integrity: sha512-UjcYfl3wJJdcabGKk8lgetPvhi1Et7VDc3sYr9EyhNBeB00YD4vHgPBp+oMVoG/dDWCc6ASbmzPNV6jADTwh8Q==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-convert-values@5.1.3(postcss@8.4.31): + resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - browserslist: 4.20.3 - postcss: 8.4.14 + browserslist: 4.23.0 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true - /postcss-discard-comments/5.1.1_postcss@8.4.14: - resolution: - { - integrity: sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-discard-comments@5.1.2(postcss@8.4.31): + resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.14 + postcss: 8.4.31 dev: true - /postcss-discard-duplicates/5.1.0_postcss@8.4.14: - resolution: - { - integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-discard-duplicates@5.1.0(postcss@8.4.31): + resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.14 + postcss: 8.4.31 dev: true - /postcss-discard-empty/5.1.1_postcss@8.4.14: - resolution: - { - integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-discard-empty@5.1.1(postcss@8.4.31): + resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.14 + postcss: 8.4.31 dev: true - /postcss-discard-overridden/5.1.0_postcss@8.4.14: - resolution: - { - integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-discard-overridden@5.1.0(postcss@8.4.31): + resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.14 + postcss: 8.4.31 + dev: true + + /postcss-html@1.5.0: + resolution: {integrity: sha512-kCMRWJRHKicpA166kc2lAVUGxDZL324bkj/pVOb6RhjB0Z5Krl7mN0AsVkBhVIRZZirY0lyQXG38HCVaoKVNoA==} + engines: {node: ^12 || >=14} + dependencies: + htmlparser2: 8.0.1 + js-tokens: 8.0.0 + postcss: 8.4.31 + postcss-safe-parser: 6.0.0(postcss@8.4.31) dev: true - /postcss-import/14.1.0_postcss@8.4.14: - resolution: - { - integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==, - } - engines: { node: '>=10.0.0' } + /postcss-import@14.1.0(postcss@8.4.31): + resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} + engines: {node: '>=10.0.0'} peerDependencies: postcss: ^8.0.0 dependencies: - postcss: 8.4.14 + postcss: 8.4.31 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.0 + resolve: 1.22.1 dev: true - /postcss-import/14.1.0_postcss@8.4.16: - resolution: - { - integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==, - } - engines: { node: '>=10.0.0' } + /postcss-import@15.1.0(postcss@8.4.31): + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} peerDependencies: postcss: ^8.0.0 dependencies: - postcss: 8.4.16 + postcss: 8.4.31 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.0 + resolve: 1.22.8 dev: true - /postcss-js/4.0.0_postcss@8.4.16: - resolution: - { - integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==, - } - engines: { node: ^12 || ^14 || >= 16 } + /postcss-js@4.0.1(postcss@8.4.31): + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} peerDependencies: - postcss: ^8.3.3 + postcss: ^8.4.21 dependencies: camelcase-css: 2.0.1 - postcss: 8.4.16 + postcss: 8.4.31 dev: true - /postcss-load-config/3.1.4_aql62wi6xyfadafedejj7u7pli: - resolution: - { - integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==, - } - engines: { node: '>= 10' } + /postcss-load-config@3.1.4(postcss@8.4.31): + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} peerDependencies: postcss: '>=8.0.9' ts-node: '>=9.0.0' @@ -8076,18 +10973,14 @@ packages: ts-node: optional: true dependencies: - lilconfig: 2.0.5 - postcss: 8.4.16 - ts-node: 10.7.0_2jmrorcux6nsf7sy4ly74hophu + lilconfig: 2.0.6 + postcss: 8.4.31 yaml: 1.10.2 dev: true - /postcss-load-config/3.1.4_gimuzhxoryaoe5ripiipjtmpcu: - resolution: - { - integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==, - } - engines: { node: '>= 10' } + /postcss-load-config@3.1.4(postcss@8.5.1): + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} peerDependencies: postcss: '>=8.0.9' ts-node: '>=9.0.0' @@ -8097,1696 +10990,1773 @@ packages: ts-node: optional: true dependencies: - lilconfig: 2.0.5 - postcss: 8.4.14 - ts-node: 10.7.0_2jmrorcux6nsf7sy4ly74hophu + lilconfig: 2.0.6 + postcss: 8.5.1 yaml: 1.10.2 dev: true - /postcss-media-query-parser/0.2.3: - resolution: - { - integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==, - } + /postcss-load-config@4.0.2(postcss@8.4.31): + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 3.0.0 + postcss: 8.4.31 + yaml: 2.3.4 dev: true - /postcss-merge-longhand/5.1.4_postcss@8.4.14: - resolution: - { - integrity: sha512-hbqRRqYfmXoGpzYKeW0/NCZhvNyQIlQeWVSao5iKWdyx7skLvCfQFGIUsP9NUs3dSbPac2IC4Go85/zG+7MlmA==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-merge-longhand@5.1.7(postcss@8.4.31): + resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.14 + postcss: 8.4.31 postcss-value-parser: 4.2.0 - stylehacks: 5.1.0_postcss@8.4.14 + stylehacks: 5.1.1(postcss@8.4.31) dev: true - /postcss-merge-rules/5.1.1_postcss@8.4.14: - resolution: - { - integrity: sha512-8wv8q2cXjEuCcgpIB1Xx1pIy8/rhMPIQqYKNzEdyx37m6gpq83mQQdCxgIkFgliyEnKvdwJf/C61vN4tQDq4Ww==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-merge-rules@5.1.3(postcss@8.4.31): + resolution: {integrity: sha512-LbLd7uFC00vpOuMvyZop8+vvhnfRGpp2S+IMQKeuOZZapPRY4SMq5ErjQeHbHsjCUgJkRNrlU+LmxsKIqPKQlA==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - browserslist: 4.20.3 + browserslist: 4.23.0 caniuse-api: 3.0.0 - cssnano-utils: 3.1.0_postcss@8.4.14 - postcss: 8.4.14 - postcss-selector-parser: 6.0.10 + cssnano-utils: 3.1.0(postcss@8.4.31) + postcss: 8.4.31 + postcss-selector-parser: 6.0.13 dev: true - /postcss-minify-font-values/5.1.0_postcss@8.4.14: - resolution: - { - integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-minify-font-values@5.1.0(postcss@8.4.31): + resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.14 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true - /postcss-minify-gradients/5.1.1_postcss@8.4.14: - resolution: - { - integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-minify-gradients@5.1.1(postcss@8.4.31): + resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - colord: 2.9.2 - cssnano-utils: 3.1.0_postcss@8.4.14 - postcss: 8.4.14 + colord: 2.9.3 + cssnano-utils: 3.1.0(postcss@8.4.31) + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true - /postcss-minify-params/5.1.3_postcss@8.4.14: - resolution: - { - integrity: sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-minify-params@5.1.4(postcss@8.4.31): + resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - browserslist: 4.20.3 - cssnano-utils: 3.1.0_postcss@8.4.14 - postcss: 8.4.14 + browserslist: 4.23.0 + cssnano-utils: 3.1.0(postcss@8.4.31) + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true - /postcss-minify-selectors/5.2.0_postcss@8.4.14: - resolution: - { - integrity: sha512-vYxvHkW+iULstA+ctVNx0VoRAR4THQQRkG77o0oa4/mBS0OzGvvzLIvHDv/nNEM0crzN2WIyFU5X7wZhaUK3RA==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-minify-selectors@5.2.1(postcss@8.4.31): + resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.14 - postcss-selector-parser: 6.0.10 + postcss: 8.4.31 + postcss-selector-parser: 6.0.13 dev: true - /postcss-nested/5.0.6_postcss@8.4.16: - resolution: - { - integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==, - } - engines: { node: '>=12.0' } + /postcss-nested@6.0.1(postcss@8.4.31): + resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} + engines: {node: '>=12.0'} peerDependencies: postcss: ^8.2.14 dependencies: - postcss: 8.4.16 - postcss-selector-parser: 6.0.10 + postcss: 8.4.31 + postcss-selector-parser: 6.0.13 dev: true - /postcss-normalize-charset/5.1.0_postcss@8.4.14: - resolution: - { - integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-normalize-charset@5.1.0(postcss@8.4.31): + resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.14 + postcss: 8.4.31 dev: true - /postcss-normalize-display-values/5.1.0_postcss@8.4.14: - resolution: - { - integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-normalize-display-values@5.1.0(postcss@8.4.31): + resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.14 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-positions/5.1.0_postcss@8.4.14: - resolution: - { - integrity: sha512-8gmItgA4H5xiUxgN/3TVvXRoJxkAWLW6f/KKhdsH03atg0cB8ilXnrB5PpSshwVu/dD2ZsRFQcR1OEmSBDAgcQ==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-normalize-positions@5.1.1(postcss@8.4.31): + resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.14 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-repeat-style/5.1.0_postcss@8.4.14: - resolution: - { - integrity: sha512-IR3uBjc+7mcWGL6CtniKNQ4Rr5fTxwkaDHwMBDGGs1x9IVRkYIT/M4NelZWkAOBdV6v3Z9S46zqaKGlyzHSchw==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-normalize-repeat-style@5.1.1(postcss@8.4.31): + resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.14 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-string/5.1.0_postcss@8.4.14: - resolution: - { - integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-normalize-string@5.1.0(postcss@8.4.31): + resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.14 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-timing-functions/5.1.0_postcss@8.4.14: - resolution: - { - integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-normalize-timing-functions@5.1.0(postcss@8.4.31): + resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.14 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-unicode/5.1.0_postcss@8.4.14: - resolution: - { - integrity: sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-normalize-unicode@5.1.1(postcss@8.4.31): + resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - browserslist: 4.20.3 - postcss: 8.4.14 + browserslist: 4.23.0 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-url/5.1.0_postcss@8.4.14: - resolution: - { - integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-normalize-url@5.1.0(postcss@8.4.31): + resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: normalize-url: 6.1.0 - postcss: 8.4.14 + postcss: 8.4.31 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-normalize-whitespace@5.1.1(postcss@8.4.31): + resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.31 + postcss-value-parser: 4.2.0 + dev: true + + /postcss-ordered-values@5.1.3(postcss@8.4.31): + resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + cssnano-utils: 3.1.0(postcss@8.4.31) + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true - /postcss-normalize-whitespace/5.1.1_postcss@8.4.14: - resolution: - { - integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-reduce-initial@5.1.1(postcss@8.4.31): + resolution: {integrity: sha512-//jeDqWcHPuXGZLoolFrUXBDyuEGbr9S2rMo19bkTIjBQ4PqkaO+oI8wua5BOUxpfi97i3PCoInsiFIEBfkm9w==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.23.0 + caniuse-api: 3.0.0 + postcss: 8.4.31 + dev: true + + /postcss-reduce-transforms@5.1.0(postcss@8.4.31): + resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - postcss: 8.4.14 + postcss: 8.4.31 postcss-value-parser: 4.2.0 dev: true - /postcss-ordered-values/5.1.1_postcss@8.4.14: - resolution: - { - integrity: sha512-7lxgXF0NaoMIgyihL/2boNAEZKiW0+HkMhdKMTD93CjW8TdCy2hSdj8lsAo+uwm7EDG16Da2Jdmtqpedl0cMfw==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /postcss-reporter@7.0.5(postcss@8.4.31): + resolution: {integrity: sha512-glWg7VZBilooZGOFPhN9msJ3FQs19Hie7l5a/eE6WglzYqVeH3ong3ShFcp9kDWJT1g2Y/wd59cocf9XxBtkWA==} + engines: {node: '>=10'} + peerDependencies: + postcss: ^8.1.0 + dependencies: + picocolors: 1.0.0 + postcss: 8.4.31 + thenby: 1.3.4 + dev: true + + /postcss-resolve-nested-selector@0.1.1: + resolution: {integrity: sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==} + dev: true + + /postcss-safe-parser@6.0.0(postcss@8.4.31): + resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.3.3 + dependencies: + postcss: 8.4.31 + dev: true + + /postcss-safe-parser@6.0.0(postcss@8.5.1): + resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.3.3 + dependencies: + postcss: 8.5.1 + dev: true + + /postcss-scss@4.0.9(postcss@8.5.3): + resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.4.29 + dependencies: + postcss: 8.5.3 + dev: true + + /postcss-selector-parser@6.0.13: + resolution: {integrity: sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + dev: true + + /postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + dev: true + + /postcss-svgo@5.1.0(postcss@8.4.31): + resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - cssnano-utils: 3.1.0_postcss@8.4.14 - postcss: 8.4.14 + postcss: 8.4.31 postcss-value-parser: 4.2.0 + svgo: 2.8.0 + dev: true + + /postcss-unique-selectors@5.1.1(postcss@8.4.31): + resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.31 + postcss-selector-parser: 6.0.13 + dev: true + + /postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + dev: true + + /postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.8 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + + /postcss@8.5.1: + resolution: {integrity: sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + dev: true + + /postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + /prelude-ls@1.1.2: + resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} + engines: {node: '>= 0.8.0'} + dev: true + + /prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true + + /prettier-plugin-svelte@3.3.3(prettier@3.5.3)(svelte@5.25.5): + resolution: {integrity: sha512-yViK9zqQ+H2qZD1w/bH7W8i+bVfKrD8GIFjkFe4Thl6kCT9SlAsXVNmt3jCvQOCsnOhcvYgsoVlRV/Eu6x5nNw==} + peerDependencies: + prettier: ^3.0.0 + svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 + dependencies: + prettier: 3.5.3 + svelte: 5.25.5 + dev: true + + /prettier-plugin-tailwindcss@0.5.4(prettier-plugin-svelte@3.3.3)(prettier@3.5.3): + resolution: {integrity: sha512-QZzzB1bID6qPsKHTeA9qPo1APmmxfFrA5DD3LQ+vbTmAnY40eJI7t9Q1ocqel2EKMWNPLJqdTDWZj1hKYgqSgg==} + engines: {node: '>=14.21.3'} + peerDependencies: + '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-pug': '*' + '@shopify/prettier-plugin-liquid': '*' + '@shufo/prettier-plugin-blade': '*' + '@trivago/prettier-plugin-sort-imports': '*' + prettier: ^3.0 + prettier-plugin-astro: '*' + prettier-plugin-css-order: '*' + prettier-plugin-import-sort: '*' + prettier-plugin-jsdoc: '*' + prettier-plugin-marko: '*' + prettier-plugin-organize-attributes: '*' + prettier-plugin-organize-imports: '*' + prettier-plugin-style-order: '*' + prettier-plugin-svelte: '*' + prettier-plugin-twig-melody: '*' + peerDependenciesMeta: + '@ianvs/prettier-plugin-sort-imports': + optional: true + '@prettier/plugin-pug': + optional: true + '@shopify/prettier-plugin-liquid': + optional: true + '@shufo/prettier-plugin-blade': + optional: true + '@trivago/prettier-plugin-sort-imports': + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-css-order: + optional: true + prettier-plugin-import-sort: + optional: true + prettier-plugin-jsdoc: + optional: true + prettier-plugin-marko: + optional: true + prettier-plugin-organize-attributes: + optional: true + prettier-plugin-organize-imports: + optional: true + prettier-plugin-style-order: + optional: true + prettier-plugin-svelte: + optional: true + prettier-plugin-twig-melody: + optional: true + dependencies: + prettier: 3.5.3 + prettier-plugin-svelte: 3.3.3(prettier@3.5.3)(svelte@5.25.5) + dev: true + + /prettier@3.5.3: + resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} + engines: {node: '>=14'} + hasBin: true + dev: true + + /pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + dev: true + + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + dev: true + + /pretty-hrtime@1.0.3: + resolution: {integrity: sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==} + engines: {node: '>= 0.8'} + dev: true + + /process-on-spawn@1.0.0: + resolution: {integrity: sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==} + engines: {node: '>=8'} + dependencies: + fromentries: 1.3.2 + dev: true + + /process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + dev: true + + /progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + dev: true + + /prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + dev: true + + /property-expr@2.0.6: + resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} + requiresBuild: true + dev: false + optional: true + + /property-information@6.2.0: + resolution: {integrity: sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==} + + /proto3-json-serializer@2.0.1: + resolution: {integrity: sha512-8awBvjO+FwkMd6gNoGFZyqkHZXCFd54CIYTb6De7dPaufGJ2XNW+QUNqbMr8MaAocMdb+KpsD4rxEOaTBDCffA==} + engines: {node: '>=14.0.0'} + dependencies: + protobufjs: 7.2.5 + dev: true + + /protobufjs@7.2.5: + resolution: {integrity: sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==} + engines: {node: '>=12.0.0'} + requiresBuild: true + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 18.15.3 + long: 5.2.3 + dev: true + + /proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + dev: true + + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} dev: true - /postcss-reduce-initial/5.1.0_postcss@8.4.14: - resolution: - { - integrity: sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw==, - } - engines: { node: ^10 || ^12 || >=14.0 } - peerDependencies: - postcss: ^8.2.15 + /ps-tree@1.2.0: + resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} + engines: {node: '>= 0.10'} + hasBin: true dependencies: - browserslist: 4.20.3 - caniuse-api: 3.0.0 - postcss: 8.4.14 + event-stream: 3.3.4 dev: true - /postcss-reduce-transforms/5.1.0_postcss@8.4.14: - resolution: - { - integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==, - } - engines: { node: ^10 || ^12 || >=14.0 } - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.14 - postcss-value-parser: 4.2.0 + /psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} dev: true - /postcss-reporter/7.0.5_postcss@8.4.14: - resolution: - { - integrity: sha512-glWg7VZBilooZGOFPhN9msJ3FQs19Hie7l5a/eE6WglzYqVeH3ong3ShFcp9kDWJT1g2Y/wd59cocf9XxBtkWA==, - } - engines: { node: '>=10' } - peerDependencies: - postcss: ^8.1.0 + /pump@3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} dependencies: - picocolors: 1.0.0 - postcss: 8.4.14 - thenby: 1.3.4 + end-of-stream: 1.4.4 + once: 1.4.0 dev: true - /postcss-resolve-nested-selector/0.1.1: - resolution: - { - integrity: sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==, - } + /punycode@2.2.0: + resolution: {integrity: sha512-LN6QV1IJ9ZhxWTNdktaPClrNfp8xdSAYS0Zk2ddX7XsXZAxckMHPCBcHRo0cTcEIgYPRiGEkmji3Idkh2yFtYw==} + engines: {node: '>=6'} dev: true - /postcss-safe-parser/6.0.0_postcss@8.4.14: - resolution: - { - integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==, - } - engines: { node: '>=12.0' } - peerDependencies: - postcss: ^8.3.3 + /pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + /qs@6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} dependencies: - postcss: 8.4.14 + side-channel: 1.0.6 dev: true - /postcss-selector-parser/6.0.10: - resolution: - { - integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==, - } - engines: { node: '>=4' } + /qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 + side-channel: 1.0.6 dev: true - /postcss-svgo/5.1.0_postcss@8.4.14: - resolution: - { - integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==, - } - engines: { node: ^10 || ^12 || >=14.0 } - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.14 - postcss-value-parser: 4.2.0 - svgo: 2.8.0 + /querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} dev: true - /postcss-unique-selectors/5.1.1_postcss@8.4.14: - resolution: - { - integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==, - } - engines: { node: ^10 || ^12 || >=14.0 } - peerDependencies: - postcss: ^8.2.15 - dependencies: - postcss: 8.4.14 - postcss-selector-parser: 6.0.10 + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true - /postcss-value-parser/4.2.0: - resolution: - { - integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==, - } + /quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} dev: true - /postcss/8.4.14: - resolution: - { - integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==, - } - engines: { node: ^10 || ^12 || >=14 } + /randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} dependencies: - nanoid: 3.3.4 - picocolors: 1.0.0 - source-map-js: 1.0.2 + safe-buffer: 5.2.1 dev: true - /postcss/8.4.16: - resolution: - { - integrity: sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==, - } - engines: { node: ^10 || ^12 || >=14 } - dependencies: - nanoid: 3.3.4 - picocolors: 1.0.0 - source-map-js: 1.0.2 + /range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + dev: true - /prelude-ls/1.1.2: - resolution: - { - integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==, - } - engines: { node: '>= 0.8.0' } + /raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 dev: true - /prelude-ls/1.2.1: - resolution: - { - integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, - } - engines: { node: '>= 0.8.0' } + /react-confetti@6.1.0(react@18.2.0): + resolution: {integrity: sha512-7Ypx4vz0+g8ECVxr88W9zhcQpbeujJAVqL14ZnXJ3I23mOI9/oBVTQ3dkJhUmB0D6XOtCZEM6N0Gm9PMngkORw==} + engines: {node: '>=10.18'} + peerDependencies: + react: ^16.3.0 || ^17.0.1 || ^18.0.0 + dependencies: + react: 18.2.0 + tween-functions: 1.2.0 dev: true - /prettier-plugin-tailwindcss/0.1.11_prettier@2.7.1: - resolution: - { - integrity: sha512-a28+1jvpIZQdZ/W97wOXb6VqI762MKE/TxMMuibMEHhyYsSxQA8Ek30KObd5kJI2HF1ldtSYprFayXJXi3pz8Q==, - } - engines: { node: '>=12.17.0' } + /react-dom@18.2.0(react@18.2.0): + resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: - prettier: '>=2.2.0' + react: ^18.2.0 dependencies: - prettier: 2.7.1 + loose-envify: 1.4.0 + react: 18.2.0 + scheduler: 0.23.0 dev: true - /prettier/2.7.1: - resolution: - { - integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==, - } - engines: { node: '>=10.13.0' } - hasBin: true + /react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} dev: true - /pretty-bytes/5.6.0: - resolution: - { - integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==, - } - engines: { node: '>=6' } + /react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} dev: true - /pretty-format/27.5.1: - resolution: - { - integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==, - } - engines: { node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0 } + /react@18.2.0: + resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + engines: {node: '>=0.10.0'} dependencies: - ansi-regex: 5.0.1 - ansi-styles: 5.2.0 - react-is: 17.0.2 + loose-envify: 1.4.0 dev: true - /pretty-hrtime/1.0.3: - resolution: - { - integrity: sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==, - } - engines: { node: '>= 0.8' } + /read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + dependencies: + pify: 2.3.0 dev: true - /process-nextick-args/2.0.1: - resolution: - { - integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==, - } + /read-pkg-up@8.0.0: + resolution: {integrity: sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==} + engines: {node: '>=12'} + dependencies: + find-up: 5.0.0 + read-pkg: 6.0.0 + type-fest: 1.4.0 dev: true - /promise-controller/1.0.0: - resolution: - { - integrity: sha512-goA0zA9L91tuQbUmiMinSYqlyUtEgg4fxJcjYnLYOQnrktb4o4UqciXDNXiRUPiDBPACmsr1k8jDW4r7UDq9Qw==, - } - engines: { node: '>=8' } - dev: false + /read-pkg@6.0.0: + resolution: {integrity: sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==} + engines: {node: '>=12'} + dependencies: + '@types/normalize-package-data': 2.4.1 + normalize-package-data: 3.0.3 + parse-json: 5.2.0 + type-fest: 1.4.0 + dev: true - /promise.prototype.finally/3.1.3: - resolution: - { - integrity: sha512-EXRF3fC9/0gz4qkt/f5EP5iW4kj9oFpBICNpCNOb/52+8nlHIX07FPLbi/q4qYBQ1xZqivMzTpNQSnArVASolQ==, - } - engines: { node: '>= 0.4' } + /readable-stream@3.6.0: + resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} + engines: {node: '>= 6'} dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.20.1 - dev: false + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + dev: true - /promise/8.2.0: - resolution: - { - integrity: sha512-+CMAlLHqwRYwBMXKCP+o8ns7DN+xHDUiI+0nArsiJ9y+kJVPLFxEaSw6Ha9s9H0tftxg2Yzl25wqj9G7m5wLZg==, - } + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} dependencies: - asap: 2.0.6 + picomatch: 2.3.1 dev: true - /promised-map/1.0.0: - resolution: - { - integrity: sha512-fP9VSMgcml+U2uJ9PBc4/LDQ3ZkJCH4blLNCS6gbH7RHyRZCYs91zxWHqiUy+heFiEMiB2op/qllYoFqmIqdWA==, - } - engines: { node: '>=10' } - dev: false + /readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} - /protobufjs/6.11.2: - resolution: - { - integrity: sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==, - } - hasBin: true - requiresBuild: true + /recast@0.23.6: + resolution: {integrity: sha512-9FHoNjX1yjuesMwuthAmPKabxYQdOgihFYmT5ebXfYGBcnqXZf3WOVz+5foEZ8Y83P4ZY6yQD5GMmtV+pgCCAQ==} + engines: {node: '>= 4'} dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/base64': 1.1.2 - '@protobufjs/codegen': 2.0.4 - '@protobufjs/eventemitter': 1.1.0 - '@protobufjs/fetch': 1.1.0 - '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.0 - '@protobufjs/path': 1.1.2 - '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.0 - '@types/long': 4.0.2 - '@types/node': 17.0.41 - long: 4.0.0 + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.5.0 dev: true - /protobufjs/6.11.3: - resolution: - { - integrity: sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==, - } - hasBin: true - requiresBuild: true + /redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/base64': 1.1.2 - '@protobufjs/codegen': 2.0.4 - '@protobufjs/eventemitter': 1.1.0 - '@protobufjs/fetch': 1.1.0 - '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.0 - '@protobufjs/path': 1.1.2 - '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.0 - '@types/long': 4.0.2 - '@types/node': 17.0.41 - long: 4.0.0 + indent-string: 4.0.0 + strip-indent: 3.0.0 dev: true - /protobufjs/7.1.2: - resolution: - { - integrity: sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==, - } - engines: { node: '>=12.0.0' } - requiresBuild: true + /redent@4.0.0: + resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==} + engines: {node: '>=12'} dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/base64': 1.1.2 - '@protobufjs/codegen': 2.0.4 - '@protobufjs/eventemitter': 1.1.0 - '@protobufjs/fetch': 1.1.0 - '@protobufjs/float': 1.0.2 - '@protobufjs/inquire': 1.1.0 - '@protobufjs/path': 1.1.2 - '@protobufjs/pool': 1.1.0 - '@protobufjs/utf8': 1.1.0 - '@types/node': 17.0.41 - long: 5.2.0 + indent-string: 5.0.0 + strip-indent: 4.0.0 dev: true - /proxy-from-env/1.0.0: - resolution: - { - integrity: sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==, - } - dev: true + /regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + dev: false - /psl/1.8.0: - resolution: - { - integrity: sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==, - } - dev: true + /regenerator-runtime@0.14.0: + resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} - /pump/3.0.0: - resolution: - { - integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==, - } + /regexp.prototype.flags@1.4.3: + resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} + engines: {node: '>= 0.4'} dependencies: - end-of-stream: 1.4.4 - once: 1.4.0 + call-bind: 1.0.7 + define-properties: 1.2.0 + functions-have-names: 1.2.3 dev: true - /punycode/2.1.1: - resolution: - { - integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==, - } - engines: { node: '>=6' } + /regexp.prototype.flags@1.5.0: + resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.0 + functions-have-names: 1.2.3 dev: true - /qs/6.5.3: - resolution: - { - integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==, - } - engines: { node: '>=0.6' } + /regexpp@3.2.0: + resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} + engines: {node: '>=8'} dev: true - /queue-microtask/1.2.3: - resolution: - { - integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, - } + /rehype-document@6.1.0: + resolution: {integrity: sha512-znEODHIhSjfBlvFO6z9k/6z7lJor1hqqpYy15W5vj/VLxcWsCL22hBdDSai5tYOymmGOInDLvepmS+6MIIXjFg==} + dependencies: + '@types/hast': 2.3.5 + hastscript: 7.2.0 + unified: 10.1.2 dev: true - /quick-lru/4.0.1: - resolution: - { - integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==, - } - engines: { node: '>=8' } + /rehype-format@4.0.1: + resolution: {integrity: sha512-HA92WeqFri00yiClrz54IIpM9no2DH9Mgy5aFmInNODoAYn+hN42a6oqJTIie2nj0HwFyV7JvOYx5YHBphN8mw==} + dependencies: + '@types/hast': 2.3.5 + hast-util-embedded: 2.0.1 + hast-util-is-element: 2.1.3 + hast-util-phrasing: 2.0.2 + hast-util-whitespace: 2.0.1 + html-whitespace-sensitive-tag-names: 2.0.0 + rehype-minify-whitespace: 5.0.1 + unified: 10.1.2 + unist-util-visit-parents: 5.1.3 dev: true - /quick-lru/5.1.1: - resolution: - { - integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==, - } - engines: { node: '>=10' } + /rehype-minify-whitespace@5.0.1: + resolution: {integrity: sha512-PPp4lWJiBPlePI/dv1BeYktbwkfgXkrK59MUa+tYbMPgleod+4DvFK2PLU0O0O60/xuhHfiR9GUIUlXTU8sRIQ==} + dependencies: + '@types/hast': 2.3.5 + hast-util-embedded: 2.0.1 + hast-util-is-element: 2.1.3 + hast-util-whitespace: 2.0.1 + unified: 10.1.2 + unist-util-is: 5.2.1 dev: true - /randombytes/2.1.0: - resolution: - { - integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==, - } + /rehype-stringify@9.0.4: + resolution: {integrity: sha512-Uk5xu1YKdqobe5XpSskwPvo1XeHUUucWEQSl8hTrXt5selvca1e8K1EZ37E6YoZ4BT8BCqCdVfQW7OfHfthtVQ==} dependencies: - safe-buffer: 5.1.2 + '@types/hast': 2.3.5 + hast-util-to-html: 8.0.4 + unified: 10.1.2 dev: true - /react-is/17.0.2: - resolution: - { - integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==, - } + /release-zalgo@1.0.0: + resolution: {integrity: sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==} + engines: {node: '>=4'} + dependencies: + es6-error: 4.1.1 dev: true - /read-cache/1.0.0: - resolution: - { - integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==, - } + /remark-gfm@3.0.1: + resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==} dependencies: - pify: 2.3.0 + '@types/mdast': 3.0.12 + mdast-util-gfm: 2.0.2 + micromark-extension-gfm: 2.0.3 + unified: 10.1.2 + transitivePeerDependencies: + - supports-color dev: true - /read-pkg-up/7.0.1: - resolution: - { - integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==, - } - engines: { node: '>=8' } + /remark-parse@10.0.2: + resolution: {integrity: sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==} dependencies: - find-up: 4.1.0 - read-pkg: 5.2.0 - type-fest: 0.8.1 + '@types/mdast': 3.0.12 + mdast-util-from-markdown: 1.3.1 + unified: 10.1.2 + transitivePeerDependencies: + - supports-color dev: true - /read-pkg/5.2.0: - resolution: - { - integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==, - } - engines: { node: '>=8' } + /remark-stringify@10.0.3: + resolution: {integrity: sha512-koyOzCMYoUHudypbj4XpnAKFbkddRMYZHwghnxd7ue5210WzGw6kOBwauJTRUMq16jsovXx8dYNvSSWP89kZ3A==} dependencies: - '@types/normalize-package-data': 2.4.1 - normalize-package-data: 2.5.0 - parse-json: 5.2.0 - type-fest: 0.6.0 - dev: true + '@types/mdast': 3.0.12 + mdast-util-to-markdown: 1.5.0 + unified: 10.1.2 - /readable-stream/2.3.7: - resolution: - { - integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==, - } + /remark-toc@8.0.1: + resolution: {integrity: sha512-7he2VOm/cy13zilnOTZcyAoyoolV26ULlon6XyCFU+vG54Z/LWJnwphj/xKIDLOt66QmJUgTyUvLVHi2aAElyg==} dependencies: - core-util-is: 1.0.2 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 + '@types/mdast': 3.0.12 + mdast-util-toc: 6.1.1 + unified: 10.1.2 dev: true - /readable-stream/3.6.0: - resolution: - { - integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==, - } - engines: { node: '>= 6' } + /remark@14.0.3: + resolution: {integrity: sha512-bfmJW1dmR2LvaMJuAnE88pZP9DktIFYXazkTfOIKZzi3Knk9lT0roItIA24ydOucI3bV/g/tXBA6hzqq3FV9Ew==} dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 + '@types/mdast': 3.0.12 + remark-parse: 10.0.2 + remark-stringify: 10.0.3 + unified: 10.1.2 + transitivePeerDependencies: + - supports-color dev: true - /readdirp/3.6.0: - resolution: - { - integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==, - } - engines: { node: '>=8.10.0' } - dependencies: - picomatch: 2.3.1 + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} dev: true - /redent/3.0.0: - resolution: - { - integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==, - } - engines: { node: '>=8' } - dependencies: - indent-string: 4.0.0 - strip-indent: 3.0.0 + /require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} dev: true - /regenerator-runtime/0.13.9: - resolution: - { - integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==, - } + /require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} dev: true - /regexp.prototype.flags/1.4.3: - resolution: - { - integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==, - } - engines: { node: '>= 0.4' } - dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - functions-have-names: 1.2.3 - dev: false + /requireindex@1.2.0: + resolution: {integrity: sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==} + engines: {node: '>=0.10.5'} + dev: true - /regexpp/3.2.0: - resolution: - { - integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==, - } - engines: { node: '>=8' } + /requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} dev: true - /request-progress/3.0.0: - resolution: - { - integrity: sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==, - } + /resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} dependencies: - throttleit: 1.0.0 + resolve-from: 5.0.0 dev: true - /require-directory/2.1.1: - resolution: - { - integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==, - } - engines: { node: '>=0.10.0' } + /resolve-dir@0.1.1: + resolution: {integrity: sha512-QxMPqI6le2u0dCLyiGzgy92kjkkL6zO0XyvHzjdTNH3zM6e5Hz3BwG6+aEyNgiQ5Xz6PwTwgQEj3U50dByPKIA==} + engines: {node: '>=0.10.0'} + dependencies: + expand-tilde: 1.2.2 + global-modules: 0.2.3 dev: true - /require-from-string/2.0.2: - resolution: - { - integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==, - } - engines: { node: '>=0.10.0' } + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} dev: true - /resolve-from/4.0.0: - resolution: - { - integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, - } - engines: { node: '>=4' } + /resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} dev: true - /resolve-from/5.0.0: - resolution: - { - integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==, - } - engines: { node: '>=8' } + /resolve.exports@2.0.2: + resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} + engines: {node: '>=10'} dev: true - /resolve/1.22.0: - resolution: - { - integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==, - } + /resolve@1.22.1: + resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} hasBin: true dependencies: - is-core-module: 2.9.0 + is-core-module: 2.13.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 dev: true - /resolve/1.22.1: - resolution: - { - integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==, - } + /resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true dependencies: - is-core-module: 2.9.0 + is-core-module: 2.13.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 dev: true - /restore-cursor/3.1.0: - resolution: - { - integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==, - } - engines: { node: '>=8' } + /restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} dependencies: onetime: 5.1.2 signal-exit: 3.0.7 dev: true - /reusify/1.0.4: - resolution: - { - integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==, - } - engines: { iojs: '>=1.0.0', node: '>=0.10.0' } + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} dev: true - /rfdc/1.3.0: - resolution: - { - integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==, - } + /rfdc@1.3.0: + resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} dev: true - /rimraf/2.7.1: - resolution: - { - integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==, - } + /rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true dependencies: glob: 7.2.3 dev: true - /rimraf/3.0.2: - resolution: - { - integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==, - } + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} hasBin: true dependencies: glob: 7.2.3 dev: true - /rollup-pluginutils/2.8.2: - resolution: - { - integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==, - } - dependencies: - estree-walker: 0.6.1 - dev: true - - /rollup/2.73.0: - resolution: - { - integrity: sha512-h/UngC3S4Zt28mB3g0+2YCMegT5yoftnQplwzPqGZcKvlld5e+kT/QRmJiL+qxGyZKOYpgirWGdLyEO1b0dpLQ==, - } - engines: { node: '>=10.0.0' } + /rimraf@4.3.1: + resolution: {integrity: sha512-GfHJHBzFQra23IxDzIdBqhOWfbtdgS1/dCHrDy+yvhpoJY5TdwdT28oWaHWfRpKFDLd3GZnGTx6Mlt4+anbsxQ==} + engines: {node: '>=14'} hasBin: true - optionalDependencies: - fsevents: 2.3.2 + dependencies: + glob: 9.3.5 dev: true - /rollup/2.78.1: - resolution: - { - integrity: sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==, - } - engines: { node: '>=10.0.0' } + /rollup@4.40.1: + resolution: {integrity: sha512-C5VvvgCCyfyotVITIAv+4efVytl5F7wt+/I2i9q9GZcEXW9BP52YYOXC58igUi+LFZVHukErIIqQSWwv/M3WRw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + dependencies: + '@types/estree': 1.0.7 optionalDependencies: - fsevents: 2.3.2 - dev: true - - /run-parallel/1.2.0: - resolution: - { - integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, - } + '@rollup/rollup-android-arm-eabi': 4.40.1 + '@rollup/rollup-android-arm64': 4.40.1 + '@rollup/rollup-darwin-arm64': 4.40.1 + '@rollup/rollup-darwin-x64': 4.40.1 + '@rollup/rollup-freebsd-arm64': 4.40.1 + '@rollup/rollup-freebsd-x64': 4.40.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.40.1 + '@rollup/rollup-linux-arm-musleabihf': 4.40.1 + '@rollup/rollup-linux-arm64-gnu': 4.40.1 + '@rollup/rollup-linux-arm64-musl': 4.40.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.40.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.40.1 + '@rollup/rollup-linux-riscv64-gnu': 4.40.1 + '@rollup/rollup-linux-riscv64-musl': 4.40.1 + '@rollup/rollup-linux-s390x-gnu': 4.40.1 + '@rollup/rollup-linux-x64-gnu': 4.40.1 + '@rollup/rollup-linux-x64-musl': 4.40.1 + '@rollup/rollup-win32-arm64-msvc': 4.40.1 + '@rollup/rollup-win32-ia32-msvc': 4.40.1 + '@rollup/rollup-win32-x64-msvc': 4.40.1 + fsevents: 2.3.3 + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 dev: true - /rxjs/6.6.7: - resolution: - { - integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==, - } - engines: { npm: '>=2.0.0' } + /rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: - tslib: 1.14.1 + tslib: 2.5.0 dev: true - /rxjs/7.5.5: - resolution: - { - integrity: sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==, - } + /sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} dependencies: - tslib: 2.4.0 - dev: true + mri: 1.2.0 - /sade/1.8.1: - resolution: - { - integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==, - } - engines: { node: '>=6' } + /safe-array-concat@1.0.0: + resolution: {integrity: sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==} + engines: {node: '>=0.4'} dependencies: - mri: 1.2.0 + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + isarray: 2.0.5 dev: true - /safe-buffer/5.1.2: - resolution: - { - integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==, - } + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: true - /safe-buffer/5.2.1: - resolution: - { - integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==, - } + /safe-regex-test@1.0.0: + resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + is-regex: 1.1.4 dev: true - /safer-buffer/2.1.2: - resolution: - { - integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, - } + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: true - /sander/0.5.1: - resolution: { integrity: sha1-dB4kXiMfB8r7b98PEzrfohalAq0= } + /sander@0.5.1: + resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==} dependencies: es6-promise: 3.3.1 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 mkdirp: 0.5.6 rimraf: 2.7.1 dev: true - /sanitize-html/2.7.1: - resolution: - { - integrity: sha512-oOpe8l4J8CaBk++2haoN5yNI5beekjuHv3JRPKUx/7h40Rdr85pemn4NkvUB3TcBP7yjat574sPlcMAyv4UQig==, - } - dependencies: - deepmerge: 4.2.2 - escape-string-regexp: 4.0.0 - htmlparser2: 6.1.0 - is-plain-object: 5.0.0 - parse-srcset: 1.0.2 - postcss: 8.4.16 - dev: false - - /saxes/6.0.0: - resolution: - { - integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==, - } - engines: { node: '>=v12.22.7' } + /saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} dependencies: xmlchars: 2.2.0 dev: true - /schema-utils/2.7.1: - resolution: - { - integrity: sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==, - } - engines: { node: '>= 8.9.0' } + /scheduler@0.23.0: + resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: - '@types/json-schema': 7.0.11 - ajv: 6.12.6 - ajv-keywords: 3.5.2_ajv@6.12.6 + loose-envify: 1.4.0 dev: true - /schema-utils/3.1.1: - resolution: - { - integrity: sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==, - } - engines: { node: '>= 10.13.0' } + /schema-utils@3.3.0: + resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} + engines: {node: '>= 10.13.0'} dependencies: - '@types/json-schema': 7.0.11 + '@types/json-schema': 7.0.12 ajv: 6.12.6 - ajv-keywords: 3.5.2_ajv@6.12.6 + ajv-keywords: 3.5.2(ajv@6.12.6) dev: true - /section-matter/1.0.0: - resolution: - { - integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==, - } - engines: { node: '>=4' } - dependencies: - extend-shallow: 2.0.1 - kind-of: 6.0.3 + /semver@6.3.0: + resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} + hasBin: true dev: true - /semver/5.7.1: - resolution: - { - integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==, - } + /semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true dev: true - /semver/6.3.0: - resolution: - { - integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==, - } + /semver@7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} hasBin: true + dependencies: + lru-cache: 6.0.0 dev: true - /semver/7.3.7: - resolution: - { - integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==, - } - engines: { node: '>=10' } + /semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} hasBin: true + + /send@0.18.0: + resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + engines: {node: '>= 0.8.0'} dependencies: - lru-cache: 6.0.0 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color dev: true - /sentence-case/3.0.4: - resolution: - { - integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==, - } + /send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: '>= 0.8.0'} dependencies: - no-case: 3.0.4 - tslib: 2.4.0 - upper-case-first: 2.0.2 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color dev: true - /serialize-javascript/6.0.0: - resolution: - { - integrity: sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==, - } + /serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} dependencies: randombytes: 2.1.0 dev: true - /set-blocking/2.0.0: - resolution: - { - integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==, - } + /serve-static@1.16.0: + resolution: {integrity: sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==} + engines: {node: '>= 0.8.0'} + dependencies: + encodeurl: 1.0.2 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.18.0 + transitivePeerDependencies: + - supports-color dev: true - /shebang-command/2.0.0: - resolution: - { - integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, - } - engines: { node: '>=8' } - dependencies: - shebang-regex: 3.0.0 + /set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} dev: true - /shebang-regex/3.0.0: - resolution: - { - integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, - } - engines: { node: '>=8' } + /set-cookie-parser@2.6.0: + resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} + + /set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 dev: true - /shell-quote/1.7.3: - resolution: - { - integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==, - } + /setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} dev: true - /shiki/0.10.1: - resolution: - { - integrity: sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==, - } + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} dependencies: - jsonc-parser: 3.2.0 - vscode-oniguruma: 1.6.2 - vscode-textmate: 5.2.0 + shebang-regex: 3.0.0 + dev: true + + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} dev: true - /side-channel/1.0.4: - resolution: - { - integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==, - } + /side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.1.1 - object-inspect: 1.12.2 - dev: false + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + object-inspect: 1.13.2 + dev: true - /signal-exit/3.0.7: - resolution: - { - integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==, - } + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} dev: true - /sirv/2.0.2: - resolution: - { - integrity: sha512-4Qog6aE29nIjAOKe/wowFTxOdmbEZKb+3tsLljaBRzJwtqto0BChD2zzH0LhgCSXiI+V7X+Y45v14wBZQ1TK3w==, - } - engines: { node: '>= 10' } + /signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: true + + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: true + + /sirv@3.0.1: + resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} + engines: {node: '>=18'} dependencies: - '@polka/url': 1.0.0-next.21 - mrmime: 1.0.1 + '@polka/url': 1.0.0-next.24 + mrmime: 2.0.0 totalist: 3.0.0 + + /sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + dev: true + + /slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} dev: true - /slash/3.0.0: - resolution: - { - integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==, - } - engines: { node: '>=8' } + /slash@4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} dev: true - /slash/4.0.0: - resolution: - { - integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==, - } - engines: { node: '>=12' } + /slash@5.1.0: + resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} + engines: {node: '>=14.16'} dev: true - /slice-ansi/3.0.0: - resolution: - { - integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==, - } - engines: { node: '>=8' } + /slice-ansi@3.0.0: + resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} + engines: {node: '>=8'} dependencies: ansi-styles: 4.3.0 astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 dev: true - /slice-ansi/4.0.0: - resolution: - { - integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==, - } - engines: { node: '>=10' } + /slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} dependencies: ansi-styles: 4.3.0 astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 dev: true - /slice-ansi/5.0.0: - resolution: - { - integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==, - } - engines: { node: '>=12' } + /slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} dependencies: - ansi-styles: 6.1.0 + ansi-styles: 6.2.1 is-fullwidth-code-point: 4.0.0 dev: true - /snake-case/3.0.4: - resolution: - { - integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==, - } - dependencies: - dot-case: 3.0.4 - tslib: 2.4.0 - dev: true - - /sorcery/0.10.0: - resolution: { integrity: sha1-iukK19fLBfxZ8asMY3hF1cFaUrc= } + /sorcery@0.11.0: + resolution: {integrity: sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==} hasBin: true dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 buffer-crc32: 0.2.13 - minimist: 1.2.6 + minimist: 1.2.8 sander: 0.5.1 - sourcemap-codec: 1.4.8 dev: true - /source-map-js/1.0.2: - resolution: - { - integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==, - } - engines: { node: '>=0.10.0' } + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: true + + /source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + /source-map-loader@4.0.2(webpack@5.94.0): + resolution: {integrity: sha512-oYwAqCuL0OZhBoSgmdrLa7mv9MjommVMiQIWgcztf+eS4+8BfcUee6nenFnDhKOhzAVnk5gpZdfnz1iiBv+5sg==} + engines: {node: '>= 14.15.0'} + peerDependencies: + webpack: ^5.72.1 + dependencies: + iconv-lite: 0.6.3 + source-map-js: 1.2.1 + webpack: 5.94.0(@swc/core@1.10.7)(esbuild@0.25.0) + dev: true + + /source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + + /source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + /source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + dev: true + + /space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + /spawn-wrap@2.0.0: + resolution: {integrity: sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==} + engines: {node: '>=8'} + dependencies: + foreground-child: 2.0.0 + is-windows: 1.0.2 + make-dir: 3.1.0 + rimraf: 3.0.2 + signal-exit: 3.0.7 + which: 2.0.2 + dev: true + + /spawnd@5.0.0: + resolution: {integrity: sha512-28+AJr82moMVWolQvlAIv3JcYDkjkFTEmfDc503wxrF5l2rQ3dFz6DpbXp3kD4zmgGGldfM4xM4v1sFj/ZaIOA==} + dependencies: + exit: 0.1.2 + signal-exit: 3.0.7 + tree-kill: 1.2.2 + wait-port: 0.2.14 + transitivePeerDependencies: + - supports-color + dev: true + + /spdx-correct@3.1.1: + resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.12 + dev: true + + /spdx-exceptions@2.3.0: + resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + dev: true + + /spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + dependencies: + spdx-exceptions: 2.3.0 + spdx-license-ids: 3.0.12 + dev: true - /source-map-support/0.5.21: - resolution: - { - integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==, - } - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 + /spdx-license-ids@3.0.12: + resolution: {integrity: sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==} dev: true - /source-map/0.6.1: - resolution: - { - integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, - } - engines: { node: '>=0.10.0' } + /split@0.3.3: + resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} + dependencies: + through: 2.3.8 dev: true - /sourcemap-codec/1.4.8: - resolution: - { - integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==, - } + /sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: true - /spawn-command/0.0.2-1: - resolution: { integrity: sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A= } + /stable@0.1.8: + resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} + deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' dev: true - /spdx-correct/3.1.1: - resolution: - { - integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==, - } + /stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} dependencies: - spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.11 + escape-string-regexp: 2.0.0 dev: true - /spdx-exceptions/2.3.0: - resolution: - { - integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==, - } + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} dev: true - /spdx-expression-parse/3.0.1: - resolution: - { - integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==, - } - dependencies: - spdx-exceptions: 2.3.0 - spdx-license-ids: 3.0.11 + /statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} dev: true - /spdx-license-ids/3.0.11: - resolution: - { - integrity: sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==, - } + /std-env@3.8.1: + resolution: {integrity: sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==} dev: true - /specificity/0.4.1: - resolution: - { - integrity: sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==, - } + /storybook@8.6.11(prettier@3.5.3): + resolution: {integrity: sha512-B2wxpmq1QYS4JV7RQu1mOHD7akfoGbuoUSkx2D2GZgv/zXAHZmDpSFcTvvBBm8FAtzChI9HhITSJ0YS0ePfnJQ==} hasBin: true + peerDependencies: + prettier: ^2 || ^3 + peerDependenciesMeta: + prettier: + optional: true + dependencies: + '@storybook/core': 8.6.11(prettier@3.5.3)(storybook@8.6.11) + prettier: 3.5.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate dev: true - /sprintf-js/1.0.3: - resolution: - { - integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==, - } + /stream-combiner@0.0.4: + resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} + dependencies: + duplexer: 0.1.2 dev: true - /sshpk/1.17.0: - resolution: - { - integrity: sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==, - } - engines: { node: '>=0.10.0' } - hasBin: true + /streamx@2.22.0: + resolution: {integrity: sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==} dependencies: - asn1: 0.2.6 - assert-plus: 1.0.0 - bcrypt-pbkdf: 1.0.2 - dashdash: 1.14.1 - ecc-jsbn: 0.1.2 - getpass: 0.1.7 - jsbn: 0.1.1 - safer-buffer: 2.1.2 - tweetnacl: 0.14.5 + fast-fifo: 1.3.2 + text-decoder: 1.2.3 + optionalDependencies: + bare-events: 2.5.4 dev: true - /stable/0.1.8: - resolution: - { - integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==, - } + /string-argv@0.3.1: + resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} + engines: {node: '>=0.6.19'} dev: true - /statuses/1.5.0: - resolution: - { - integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==, - } - engines: { node: '>= 0.6' } + /string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 dev: true - /string-argv/0.3.1: - resolution: - { - integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==, - } - engines: { node: '>=0.6.19' } + /string-length@5.0.1: + resolution: {integrity: sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow==} + engines: {node: '>=12.20'} + dependencies: + char-regex: 2.0.1 + strip-ansi: 7.1.0 dev: true - /string-width/4.2.3: - resolution: - { - integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==, - } - engines: { node: '>=8' } + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 dev: true - /string-width/5.1.2: - resolution: - { - integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==, - } - engines: { node: '>=12' } + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.0.1 dev: true - /string.prototype.trimend/1.0.5: - resolution: - { - integrity: sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==, - } + /string.prototype.trim@1.2.7: + resolution: {integrity: sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==} + engines: {node: '>= 0.4'} dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.20.1 - dev: false + call-bind: 1.0.7 + define-properties: 1.2.0 + es-abstract: 1.22.1 + dev: true - /string.prototype.trimstart/1.0.5: - resolution: - { - integrity: sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==, - } + /string.prototype.trimend@1.0.6: + resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} dependencies: - call-bind: 1.0.2 - define-properties: 1.1.4 - es-abstract: 1.20.1 - dev: false + call-bind: 1.0.7 + define-properties: 1.2.0 + es-abstract: 1.22.1 + dev: true - /string_decoder/1.1.1: - resolution: - { - integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==, - } + /string.prototype.trimstart@1.0.6: + resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} dependencies: - safe-buffer: 5.1.2 + call-bind: 1.0.7 + define-properties: 1.2.0 + es-abstract: 1.22.1 dev: true - /string_decoder/1.3.0: - resolution: - { - integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==, - } + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: safe-buffer: 5.2.1 dev: true - /strip-ansi/6.0.1: - resolution: - { - integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, - } - engines: { node: '>=8' } + /stringify-entities@4.0.3: + resolution: {integrity: sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g==} + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 dev: true - /strip-ansi/7.0.1: - resolution: - { - integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==, - } - engines: { node: '>=12' } + /strip-ansi@7.0.1: + resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + dev: true + + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 dev: true - /strip-bom-string/1.0.0: - resolution: - { - integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==, - } - engines: { node: '>=0.10.0' } + /strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + dev: true + + /strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} dev: true - /strip-final-newline/2.0.0: - resolution: - { - integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==, - } - engines: { node: '>=6' } + /strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} dev: true - /strip-final-newline/3.0.0: - resolution: - { - integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==, - } - engines: { node: '>=12' } + /strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} dev: true - /strip-indent/3.0.0: - resolution: - { - integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==, - } - engines: { node: '>=8' } + /strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} dependencies: min-indent: 1.0.1 dev: true - /strip-json-comments/3.1.1: - resolution: - { - integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==, - } - engines: { node: '>=8' } + /strip-indent@4.0.0: + resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==} + engines: {node: '>=12'} + dependencies: + min-indent: 1.0.1 dev: true - /style-mod/4.0.0: - resolution: - { - integrity: sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==, - } + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} dev: true - /style-search/0.1.0: - resolution: { integrity: sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI= } + /style-mod@4.0.0: + resolution: {integrity: sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==} + dev: false + + /style-mod@4.1.2: + resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} + dev: false + + /style-search@0.1.0: + resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==} dev: true - /stylehacks/5.1.0_postcss@8.4.14: - resolution: - { - integrity: sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==, - } - engines: { node: ^10 || ^12 || >=14.0 } + /stylehacks@5.1.1(postcss@8.4.31): + resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: postcss: ^8.2.15 dependencies: - browserslist: 4.20.3 - postcss: 8.4.14 - postcss-selector-parser: 6.0.10 + browserslist: 4.23.0 + postcss: 8.4.31 + postcss-selector-parser: 6.0.13 + dev: true + + /stylelint-config-recommended@13.0.0(stylelint@15.10.3): + resolution: {integrity: sha512-EH+yRj6h3GAe/fRiyaoO2F9l9Tgg50AOFhaszyfov9v6ayXJ1IkSHwTxd7lB48FmOeSGDPLjatjO11fJpmarkQ==} + engines: {node: ^14.13.1 || >=16.0.0} + peerDependencies: + stylelint: ^15.10.0 + dependencies: + stylelint: 15.10.3 dev: true - /stylelint-config-recommended/7.0.0_stylelint@14.8.2: - resolution: - { - integrity: sha512-yGn84Bf/q41J4luis1AZ95gj0EQwRX8lWmGmBwkwBNSkpGSpl66XcPTulxGa/Z91aPoNGuIGBmFkcM1MejMo9Q==, - } + /stylelint-config-standard@34.0.0(stylelint@15.10.3): + resolution: {integrity: sha512-u0VSZnVyW9VSryBG2LSO+OQTjN7zF9XJaAJRX/4EwkmU0R2jYwmBSN10acqZisDitS0CLiEiGjX7+Hrq8TAhfQ==} + engines: {node: ^14.13.1 || >=16.0.0} peerDependencies: - stylelint: ^14.4.0 + stylelint: ^15.10.0 dependencies: - stylelint: 14.8.2 + stylelint: 15.10.3 + stylelint-config-recommended: 13.0.0(stylelint@15.10.3) dev: true - /stylelint/14.8.2: - resolution: - { - integrity: sha512-tjDfexCYfoPdl/xcDJ9Fv+Ko9cvzbDnmdiaqEn3ovXHXasi/hbkt5tSjsiReQ+ENqnz0eltaX/AOO+AlzVdcNA==, - } - engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + /stylelint@15.10.3: + resolution: {integrity: sha512-aBQMMxYvFzJJwkmg+BUUg3YfPyeuCuKo2f+LOw7yYbU8AZMblibwzp9OV4srHVeQldxvSFdz0/Xu8blq2AesiA==} + engines: {node: ^14.13.1 || >=16.0.0} hasBin: true dependencies: + '@csstools/css-parser-algorithms': 2.3.1(@csstools/css-tokenizer@2.2.0) + '@csstools/css-tokenizer': 2.2.0 + '@csstools/media-query-list-parser': 2.1.4(@csstools/css-parser-algorithms@2.3.1)(@csstools/css-tokenizer@2.2.0) + '@csstools/selector-specificity': 3.0.0(postcss-selector-parser@6.0.13) balanced-match: 2.0.0 - colord: 2.9.2 - cosmiconfig: 7.0.1 - css-functions-list: 3.0.1 + colord: 2.9.3 + cosmiconfig: 8.2.0 + css-functions-list: 3.2.0 + css-tree: 2.3.1 debug: 4.3.4 - execall: 2.0.0 - fast-glob: 3.2.11 - fastest-levenshtein: 1.0.12 + fast-glob: 3.3.1 + fastest-levenshtein: 1.0.16 file-entry-cache: 6.0.1 - get-stdin: 8.0.0 global-modules: 2.0.0 globby: 11.1.0 globjoin: 0.1.4 - html-tags: 3.2.0 - ignore: 5.2.0 + html-tags: 3.3.1 + ignore: 5.2.4 import-lazy: 4.0.0 imurmurhash: 0.1.4 is-plain-object: 5.0.0 - known-css-properties: 0.25.0 + known-css-properties: 0.28.0 mathml-tag-names: 2.1.3 - meow: 9.0.0 + meow: 10.1.5 micromatch: 4.0.5 normalize-path: 3.0.0 - normalize-selector: 0.2.0 picocolors: 1.0.0 - postcss: 8.4.14 - postcss-media-query-parser: 0.2.3 + postcss: 8.4.31 postcss-resolve-nested-selector: 0.1.1 - postcss-safe-parser: 6.0.0_postcss@8.4.14 - postcss-selector-parser: 6.0.10 + postcss-safe-parser: 6.0.0(postcss@8.4.31) + postcss-selector-parser: 6.0.13 postcss-value-parser: 4.2.0 resolve-from: 5.0.0 - specificity: 0.4.1 string-width: 4.2.3 strip-ansi: 6.0.1 style-search: 0.1.0 - supports-hyperlinks: 2.2.0 + supports-hyperlinks: 3.0.0 svg-tags: 1.0.0 - table: 6.8.0 - v8-compile-cache: 2.3.0 - write-file-atomic: 4.0.1 + table: 6.8.1 + write-file-atomic: 5.0.1 transitivePeerDependencies: - supports-color dev: true - /supports-color/5.5.0: - resolution: - { - integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==, - } - engines: { node: '>=4' } + /sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.3.10 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + dev: true + + /superstruct@2.0.2: + resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==} + engines: {node: '>=14.0.0'} + requiresBuild: true + dev: false + optional: true + + /supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} dependencies: has-flag: 3.0.0 dev: true - /supports-color/7.2.0: - resolution: - { - integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, - } - engines: { node: '>=8' } + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} dependencies: has-flag: 4.0.0 dev: true - /supports-color/8.1.1: - resolution: - { - integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==, - } - engines: { node: '>=10' } + /supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} dependencies: has-flag: 4.0.0 dev: true - /supports-hyperlinks/2.2.0: - resolution: - { - integrity: sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==, - } - engines: { node: '>=8' } + /supports-hyperlinks@3.0.0: + resolution: {integrity: sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==} + engines: {node: '>=14.18'} dependencies: has-flag: 4.0.0 supports-color: 7.2.0 dev: true - /supports-preserve-symlinks-flag/1.0.0: - resolution: - { - integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, - } - engines: { node: '>= 0.4' } + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} dev: true - /svelte-check/2.7.1_irkco2cxth74o7nd7xqin6hsem: - resolution: - { - integrity: sha512-vHVu2+SQ6ibt77iTQaq2oiOjBgGL48qqcg0ZdEOsP5pPOjgeyR9QbnaEdzdBs9nsVYBc/42haKtzb2uFqS8GVw==, - } + /svelte-ast-print@0.4.2(svelte@5.25.5): + resolution: {integrity: sha512-hRHHufbJoArFmDYQKCpCvc0xUuIEfwYksvyLYEQyH+1xb5LD5sM/IthfooCdXZQtOIqXz6xm7NmaqdfwG4kh6w==} + engines: {node: '>=18'} + peerDependencies: + svelte: ^5.0.0 + dependencies: + esrap: 1.2.2 + svelte: 5.25.5 + zimmerframe: 1.1.2 + dev: true + + /svelte-check@4.1.5(svelte@5.25.5)(typescript@5.2.2): + resolution: {integrity: sha512-Gb0T2IqBNe1tLB9EB1Qh+LOe+JB8wt2/rNBDGvkxQVvk8vNeAoG+vZgFB/3P5+zC7RWlyBlzm9dVjZFph/maIg==} + engines: {node: '>= 18.0.0'} hasBin: true peerDependencies: - svelte: ^3.24.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: '>=5.0.0' dependencies: - '@jridgewell/trace-mapping': 0.3.13 - chokidar: 3.5.3 - fast-glob: 3.2.11 - import-fresh: 3.3.0 - picocolors: 1.0.0 + '@jridgewell/trace-mapping': 0.3.25 + chokidar: 4.0.3 + fdir: 6.4.3(picomatch@4.0.2) + picocolors: 1.1.1 sade: 1.8.1 - svelte: 3.52.0 - svelte-preprocess: 4.10.6_mzb65lhesrxcsbtmqpxwfdasfq - typescript: 4.6.4 + svelte: 5.25.5 + typescript: 5.2.2 transitivePeerDependencies: - - '@babel/core' - - coffeescript - - less - - node-sass - - postcss - - postcss-load-config - - pug - - sass - - stylus - - sugarss + - picomatch dev: true - /svelte-dev-helper/1.1.9: - resolution: { integrity: sha1-fRh9tcbNu9ZNdaMvkbiZi94yc8M= } + /svelte-dev-helper@1.1.9: + resolution: {integrity: sha512-oU+Xv7Dl4kRU2kdFjsoPLfJfnt5hUhsFUZtuzI3Ku/f2iAFZqBoEuXOqK3N9ngD4dxQOmN4OKWPHVi3NeAeAfQ==} dev: true - /svelte-highlight/3.4.0: - resolution: - { - integrity: sha512-M9undFpG+4EDYrBG9CPWoOD+5dw9hpxpdO2XZijjwqwUfrEoDsODUMlGdOcqzTBjYS87JYlg60FVxZvC3cC3tg==, - } + /svelte-eslint-parser@0.43.0(svelte@5.25.5): + resolution: {integrity: sha512-GpU52uPKKcVnh8tKN5P4UZpJ/fUDndmq7wfsvoVXsyP+aY0anol7Yqo01fyrlaWGMFfm4av5DyrjlaXdLRJvGA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 + peerDependenciesMeta: + svelte: + optional: true dependencies: - highlight.js: 11.2.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + postcss: 8.5.3 + postcss-scss: 4.0.9(postcss@8.5.3) + svelte: 5.25.5 dev: true - /svelte-hmr/0.14.11_svelte@3.52.0: - resolution: - { - integrity: sha512-R9CVfX6DXxW1Kn45Jtmx+yUe+sPhrbYSUp7TkzbW0jI5fVPn6lsNG9NEs5dFg5qRhFNAoVdRw5qQDLALNKhwbQ==, - } - engines: { node: ^12.20 || ^14.13.1 || >= 16 } - peerDependencies: - svelte: '>=3.19.0' + /svelte-highlight@7.8.2: + resolution: {integrity: sha512-fJZOQtI71CIAOzv43h/cHVDABB3YfWFERGxZx4RisBVHjEuJXfnrrEOhPS0iE2uZUFyQKDWZyzPlcYyBAkH2iA==} dependencies: - svelte: 3.52.0 + highlight.js: 11.11.1 dev: true - /svelte-hmr/0.14.12_svelte@3.52.0: - resolution: - { - integrity: sha512-4QSW/VvXuqVcFZ+RhxiR8/newmwOCTlbYIezvkeN6302YFRE8cXy0naamHcjz8Y9Ce3ITTZtrHrIL0AGfyo61w==, - } - engines: { node: ^12.20 || ^14.13.1 || >= 16 } + /svelte-hmr@0.14.12(svelte@5.25.5): + resolution: {integrity: sha512-4QSW/VvXuqVcFZ+RhxiR8/newmwOCTlbYIezvkeN6302YFRE8cXy0naamHcjz8Y9Ce3ITTZtrHrIL0AGfyo61w==} + engines: {node: ^12.20 || ^14.13.1 || >= 16} peerDependencies: svelte: '>=3.19.0' dependencies: - svelte: 3.52.0 + svelte: 5.25.5 dev: true - /svelte-loader/3.1.2_svelte@3.52.0: - resolution: - { - integrity: sha512-RhVIvitb+mtIwKNyvNQoDQ0EhXg2KH8LhQiiqeJh8u6vqJyGWoMoFcYCar69TT+1iaK5IYe0wPNYJ6TILcsurw==, - } + /svelte-loader@3.2.4(svelte@5.25.5): + resolution: {integrity: sha512-e0HdDnkYH/MDx4/IfTSka5AOFg9yYJcPuoZJB5x0l60fkHjVjNvrrxr+rJegDG9J7ZymmdHt00/hdLw+QF299w==} peerDependencies: - svelte: '>3.0.0' + svelte: ^3.0.0 || ^4.0.0-next.0 || ^5.0.0-next.1 dependencies: - loader-utils: 2.0.2 - svelte: 3.52.0 + loader-utils: 2.0.4 + svelte: 5.25.5 svelte-dev-helper: 1.1.9 - svelte-hmr: 0.14.11_svelte@3.52.0 + svelte-hmr: 0.14.12(svelte@5.25.5) dev: true - /svelte-preprocess/4.10.6_mzb65lhesrxcsbtmqpxwfdasfq: - resolution: - { - integrity: sha512-I2SV1w/AveMvgIQlUF/ZOO3PYVnhxfcpNyGt8pxpUVhPfyfL/CZBkkw/KPfuFix5FJ9TnnNYMhACK3DtSaYVVQ==, - } - engines: { node: '>= 9.11.2' } + /svelte-preprocess@5.1.4(@babel/core@7.20.12)(postcss-load-config@3.1.4)(postcss@8.4.31)(svelte@5.25.5)(typescript@5.2.2): + resolution: {integrity: sha512-IvnbQ6D6Ao3Gg6ftiM5tdbR6aAETwjhHV+UKGf5bHGYR69RQvF1ho0JKPcbUON4vy4R7zom13jPjgdOWCQ5hDA==} + engines: {node: '>= 16.0.0'} requiresBuild: true peerDependencies: '@babel/core': ^7.10.2 coffeescript: ^2.5.1 less: ^3.11.3 || ^4.0.0 - node-sass: '*' postcss: ^7 || ^8 - postcss-load-config: ^2.1.0 || ^3.0.0 + postcss-load-config: ^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 pug: ^3.0.0 sass: ^1.26.8 stylus: ^0.55.0 - sugarss: ^2.0.0 - svelte: ^3.23.0 - typescript: ^3.9.5 || ^4.0.0 + sugarss: ^2.0.0 || ^3.0.0 || ^4.0.0 + svelte: ^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0 + typescript: '>=3.9.5 || ^4.0.0 || ^5.0.0' peerDependenciesMeta: '@babel/core': optional: true @@ -9794,8 +12764,6 @@ packages: optional: true less: optional: true - node-sass: - optional: true postcss: optional: true postcss-load-config: @@ -9811,52 +12779,144 @@ packages: typescript: optional: true dependencies: - '@babel/core': 7.17.12 + '@babel/core': 7.20.12 '@types/pug': 2.0.6 - '@types/sass': 1.43.1 detect-indent: 6.1.0 - magic-string: 0.25.9 - postcss: 8.4.14 - postcss-load-config: 3.1.4_gimuzhxoryaoe5ripiipjtmpcu - sorcery: 0.10.0 + magic-string: 0.30.17 + postcss: 8.4.31 + postcss-load-config: 3.1.4(postcss@8.4.31) + sorcery: 0.11.0 strip-indent: 3.0.0 - svelte: 3.52.0 - typescript: 4.6.4 + svelte: 5.25.5 + typescript: 5.2.2 dev: true - /svelte/3.52.0: - resolution: - { - integrity: sha512-FxcnEUOAVfr10vDU5dVgJN19IvqeHQCS1zfe8vayTfis9A2t5Fhx+JDe5uv/C3j//bB1umpLJ6quhgs9xyUbCQ==, - } - engines: { node: '>= 8' } + /svelte-preprocess@6.0.3(@babel/core@7.20.12)(postcss-load-config@3.1.4)(postcss@8.4.31)(svelte@5.25.5)(typescript@5.2.2): + resolution: {integrity: sha512-PLG2k05qHdhmRG7zR/dyo5qKvakhm8IJ+hD2eFRQmMLHp7X3eJnjeupUtvuRpbNiF31RjVw45W+abDwHEmP5OA==} + engines: {node: '>= 18.0.0'} + requiresBuild: true + peerDependencies: + '@babel/core': ^7.10.2 + coffeescript: ^2.5.1 + less: ^3.11.3 || ^4.0.0 + postcss: ^7 || ^8 + postcss-load-config: '>=3' + pug: ^3.0.0 + sass: ^1.26.8 + stylus: '>=0.55' + sugarss: ^2.0.0 || ^3.0.0 || ^4.0.0 + svelte: ^4.0.0 || ^5.0.0-next.100 || ^5.0.0 + typescript: ^5.0.0 + peerDependenciesMeta: + '@babel/core': + optional: true + coffeescript: + optional: true + less: + optional: true + postcss: + optional: true + postcss-load-config: + optional: true + pug: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + typescript: + optional: true + dependencies: + '@babel/core': 7.20.12 + postcss: 8.4.31 + postcss-load-config: 3.1.4(postcss@8.4.31) + svelte: 5.25.5 + typescript: 5.2.2 dev: true - /svelte2tsx/0.5.10_r4lkni65x73edzylyqv3pim744: - resolution: - { - integrity: sha512-nokQ0HTTWMcNX6tLrDLiOmJCuqjKZU9nCZ6/mVuCL3nusXdbp+9nv69VG2pCy7uQC66kV4Ls+j0WfvvJuGVnkg==, - } + /svelte2tsx@0.7.35(svelte@5.25.5)(typescript@5.2.2): + resolution: {integrity: sha512-z2lnOnrfb5nrlRfFQI8Qdz03xQqMHUfPj0j8l/fQuydrH89cCeN+v9jgDwK9GyMtdTRUkE7Neu9Gh+vfXJAfuQ==} peerDependencies: - svelte: ^3.24 - typescript: ^4.1.2 + svelte: ^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0 + typescript: ^4.9.4 || ^5.0.0 dependencies: dedent-js: 1.0.1 pascal-case: 3.1.2 - svelte: 3.52.0 - typescript: 4.6.4 + svelte: 5.25.5 + typescript: 5.2.2 + + /svelte@5.25.5: + resolution: {integrity: sha512-ULi9rkVWQJyJYZSpy6SIgSTchWadyWG1QYAUx3JAXL2gXrnhdXtoB20KmXGSNdtNyquq3eYd/gkwAkLcL5PGWw==} + engines: {node: '>=18'} + dependencies: + '@ampproject/remapping': 2.3.0 + '@jridgewell/sourcemap-codec': 1.5.0 + '@sveltejs/acorn-typescript': 1.0.5(acorn@8.14.0) + '@types/estree': 1.0.6 + acorn: 8.14.0 + aria-query: 5.3.2 + axobject-query: 4.1.0 + clsx: 2.1.1 + esm-env: 1.2.2 + esrap: 1.4.5 + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.17 + zimmerframe: 1.1.2 + + /sveltedoc-parser@4.2.1: + resolution: {integrity: sha512-sWJRa4qOfRdSORSVw9GhfDEwsbsYsegnDzBevUCF6k/Eis/QqCu9lJ6I0+d/E2wOWCjOhlcJ3+jl/Iur+5mmCw==} + engines: {node: '>=10.0.0'} + dependencies: + eslint: 8.4.1 + espree: 9.2.0 + htmlparser2-svelte: 4.1.0 + transitivePeerDependencies: + - supports-color dev: true - /svg-tags/1.0.0: - resolution: { integrity: sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q= } + /sveltekit-superforms@2.27.1(@sveltejs/kit@2.20.2)(esbuild@0.25.0)(svelte@5.25.5)(typescript@5.2.2): + resolution: {integrity: sha512-cvq2AevkZ0Zrk0w0gNM3kjcnJMtJ0jzu+2zqDoM9a+lZa+8bGpNl4YqxVkemiJNkGnFgNC8xr5xF5BlMzjookQ==} + peerDependencies: + '@sveltejs/kit': 1.x || 2.x + svelte: 3.x || 4.x || >=5.0.0-next.51 + dependencies: + '@sveltejs/kit': 2.20.2(@sveltejs/vite-plugin-svelte@5.0.3)(svelte@5.25.5)(vite@6.2.7) + devalue: 5.1.1 + memoize-weak: 1.0.2 + svelte: 5.25.5 + ts-deepmerge: 7.0.3 + optionalDependencies: + '@exodus/schemasafe': 1.3.0 + '@gcornut/valibot-json-schema': 0.42.0(esbuild@0.25.0)(typescript@5.2.2) + '@sinclair/typebox': 0.34.37 + '@typeschema/class-validator': 0.3.0(class-validator@0.14.2) + '@vinejs/vine': 3.0.1 + arktype: 2.1.20 + class-validator: 0.14.2 + effect: 3.16.10 + joi: 17.13.3 + json-schema-to-ts: 3.1.1 + superstruct: 2.0.2 + valibot: 1.1.0(typescript@5.2.2) + yup: 1.6.1 + zod: 3.25.67 + zod-to-json-schema: 3.24.6(zod@3.25.67) + transitivePeerDependencies: + - '@types/json-schema' + - esbuild + - typescript + dev: false + + /svg-tags@1.0.0: + resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} dev: true - /svgo/2.8.0: - resolution: - { - integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==, - } - engines: { node: '>=10.13.0' } + /svgo@2.8.0: + resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} + engines: {node: '>=10.13.0'} hasBin: true dependencies: '@trysound/sax': 0.2.0 @@ -9864,115 +12924,110 @@ packages: css-select: 4.3.0 css-tree: 1.1.3 csso: 4.2.0 - picocolors: 1.0.0 + picocolors: 1.1.1 stable: 0.1.8 dev: true - /symbol-tree/3.2.4: - resolution: - { - integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==, - } - dev: true - - /sync-request/6.1.0: - resolution: - { - integrity: sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==, - } - engines: { node: '>=8.0.0' } + /swc-loader@0.2.3(@swc/core@1.10.7)(webpack@5.94.0): + resolution: {integrity: sha512-D1p6XXURfSPleZZA/Lipb3A8pZ17fP4NObZvFCDjK/OKljroqDpPmsBdTraWhVBqUNpcWBQY1imWdoPScRlQ7A==} + peerDependencies: + '@swc/core': ^1.2.147 + webpack: '>=2' dependencies: - http-response-object: 3.0.2 - sync-rpc: 1.3.6 - then-request: 6.0.2 + '@swc/core': 1.10.7 + webpack: 5.94.0(@swc/core@1.10.7)(esbuild@0.25.0) dev: true - /sync-rpc/1.3.6: - resolution: - { - integrity: sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==, - } - dependencies: - get-port: 3.2.0 + /symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} dev: true - /table/6.8.0: - resolution: - { - integrity: sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==, - } - engines: { node: '>=10.0.0' } + /table@6.8.1: + resolution: {integrity: sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==} + engines: {node: '>=10.0.0'} dependencies: - ajv: 8.11.0 + ajv: 8.12.0 lodash.truncate: 4.4.2 slice-ansi: 4.0.0 string-width: 4.2.3 strip-ansi: 6.0.1 dev: true - /tailwindcss/3.1.4_ts-node@10.7.0: - resolution: - { - integrity: sha512-NrxbFV4tYsga/hpWbRyUfIaBrNMXDxx5BsHgBS4v5tlyjf+sDsgBg5m9OxjrXIqAS/uR9kicxLKP+bEHI7BSeQ==, - } - engines: { node: '>=12.13.0' } + /tailwind-merge@1.14.0: + resolution: {integrity: sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==} + dev: false + + /tailwindcss@3.4.1: + resolution: {integrity: sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==} + engines: {node: '>=14.0.0'} hasBin: true dependencies: + '@alloc/quick-lru': 5.2.0 arg: 5.0.2 chokidar: 3.5.3 - color-name: 1.1.4 - detective: 5.2.1 didyoumean: 1.2.2 dlv: 1.1.3 - fast-glob: 3.2.11 + fast-glob: 3.3.1 glob-parent: 6.0.2 is-glob: 4.0.3 - lilconfig: 2.0.5 + jiti: 1.21.0 + lilconfig: 2.1.0 + micromatch: 4.0.5 normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.0 - postcss: 8.4.16 - postcss-import: 14.1.0_postcss@8.4.16 - postcss-js: 4.0.0_postcss@8.4.16 - postcss-load-config: 3.1.4_aql62wi6xyfadafedejj7u7pli - postcss-nested: 5.0.6_postcss@8.4.16 - postcss-selector-parser: 6.0.10 - postcss-value-parser: 4.2.0 - quick-lru: 5.1.1 - resolve: 1.22.1 + postcss: 8.4.31 + postcss-import: 15.1.0(postcss@8.4.31) + postcss-js: 4.0.1(postcss@8.4.31) + postcss-load-config: 4.0.2(postcss@8.4.31) + postcss-nested: 6.0.1(postcss@8.4.31) + postcss-selector-parser: 6.0.13 + resolve: 1.22.8 + sucrase: 3.35.0 transitivePeerDependencies: - ts-node dev: true - /tapable/2.2.1: - resolution: - { - integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==, - } - engines: { node: '>=6' } + /tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + dev: true + + /tar-fs@3.0.9: + resolution: {integrity: sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==} + dependencies: + pump: 3.0.0 + tar-stream: 3.1.7 + optionalDependencies: + bare-fs: 4.1.5 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-buffer + dev: true + + /tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + dependencies: + b4a: 1.6.7 + fast-fifo: 1.3.2 + streamx: 2.22.0 dev: true - /tar/6.1.11: - resolution: - { - integrity: sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==, - } - engines: { node: '>= 10' } + /tar@6.2.0: + resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==} + engines: {node: '>=10'} dependencies: chownr: 2.0.0 fs-minipass: 2.1.0 - minipass: 3.3.4 + minipass: 5.0.0 minizlib: 2.1.2 mkdirp: 1.0.4 yallist: 4.0.0 dev: true - /terser-webpack-plugin/5.3.3_7cpexqm4fdhvdbn47hablhluku: - resolution: - { - integrity: sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ==, - } - engines: { node: '>= 10.13.0' } + /terser-webpack-plugin@5.3.10(@swc/core@1.10.7)(esbuild@0.25.0)(webpack@5.94.0): + resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} + engines: {node: '>= 10.13.0'} peerDependencies: '@swc/core': '*' esbuild: '*' @@ -9986,798 +13041,913 @@ packages: uglify-js: optional: true dependencies: - '@jridgewell/trace-mapping': 0.3.13 - esbuild: 0.13.15 + '@jridgewell/trace-mapping': 0.3.25 + '@swc/core': 1.10.7 + esbuild: 0.25.0 jest-worker: 27.5.1 - schema-utils: 3.1.1 - serialize-javascript: 6.0.0 - terser: 5.14.1 - webpack: 5.73.0_esbuild@0.13.15 - dev: true - - /terser/5.14.1: - resolution: - { - integrity: sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ==, - } - engines: { node: '>=10' } + schema-utils: 3.3.0 + serialize-javascript: 6.0.2 + terser: 5.28.1 + webpack: 5.94.0(@swc/core@1.10.7)(esbuild@0.25.0) + dev: true + + /terser@5.28.1: + resolution: {integrity: sha512-wM+bZp54v/E9eRRGXb5ZFDvinrJIOaTapx3WUokyVGZu5ucVCK55zEgGd5Dl2fSr3jUo5sDiERErUWLY6QPFyA==} + engines: {node: '>=10'} hasBin: true dependencies: - '@jridgewell/source-map': 0.3.2 - acorn: 8.8.0 + '@jridgewell/source-map': 0.3.5 + acorn: 8.14.0 commander: 2.20.3 source-map-support: 0.5.21 dev: true - /test-exclude/6.0.0: - resolution: - { - integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==, - } - engines: { node: '>=8' } + /test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} dependencies: '@istanbuljs/schema': 0.1.3 glob: 7.2.3 minimatch: 3.1.2 dev: true - /text-table/0.2.0: - resolution: - { - integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==, - } + /text-decoder@1.2.3: + resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + dependencies: + b4a: 1.6.7 + dev: true + + /text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true - /then-request/6.0.2: - resolution: - { - integrity: sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==, - } - engines: { node: '>=6.0.0' } + /thenby@1.3.4: + resolution: {integrity: sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==} + dev: true + + /thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} dependencies: - '@types/concat-stream': 1.6.1 - '@types/form-data': 0.0.33 - '@types/node': 8.10.66 - '@types/qs': 6.9.7 - caseless: 0.12.0 - concat-stream: 1.6.2 - form-data: 2.3.3 - http-basic: 8.1.3 - http-response-object: 3.0.2 - promise: 8.2.0 - qs: 6.5.3 + thenify: 3.3.1 + dev: true + + /thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + dependencies: + any-promise: 1.3.0 + dev: true + + /through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: true - /thenby/1.3.4: - resolution: - { - integrity: sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==, - } + /tiny-case@1.0.3: + resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} + requiresBuild: true + dev: false + optional: true + + /tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} dev: true - /throttleit/1.0.0: - resolution: - { - integrity: sha512-rkTVqu6IjfQ/6+uNuuc3sZek4CEYxTJom3IktzgdSxcZqdARuebbA/f4QmAxMQIxqq9ZLEUkSYqvuk1I6VKq4g==, - } + /tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} dev: true - /through/2.3.8: - resolution: - { - integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==, - } + /tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} dev: true - /tiny-glob/0.2.9: - resolution: - { - integrity: sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==, - } + /tinyglobby@0.2.12: + resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==} + engines: {node: '>=12.0.0'} dependencies: - globalyzer: 0.1.0 - globrex: 0.1.2 + fdir: 6.4.3(picomatch@4.0.2) + picomatch: 4.0.2 dev: true - /tinypool/0.1.3: - resolution: - { - integrity: sha512-2IfcQh7CP46XGWGGbdyO4pjcKqsmVqFAPcXfPxcPXmOWt9cYkTP9HcDmGgsfijYoAEc4z9qcpM/BaBz46Y9/CQ==, - } - engines: { node: '>=14.0.0' } + /tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} dev: true - /tinyspy/0.3.3: - resolution: - { - integrity: sha512-gRiUR8fuhUf0W9lzojPf1N1euJYA30ISebSfgca8z76FOvXtVXqd5ojEIaKLWbDQhAaC3ibxZIjqbyi4ybjcTw==, - } - engines: { node: '>=14.0.0' } + /tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} dev: true - /tmp/0.2.1: - resolution: - { - integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==, - } - engines: { node: '>=8.17.0' } - dependencies: - rimraf: 3.0.2 + /tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + dev: true + + /tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} dev: true - /to-fast-properties/2.0.0: - resolution: { integrity: sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= } - engines: { node: '>=4' } + /tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} dev: true - /to-regex-range/5.0.1: - resolution: - { - integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, - } - engines: { node: '>=8.0' } + /to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + dev: true + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 dev: true - /totalist/3.0.0: - resolution: - { - integrity: sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==, - } - engines: { node: '>=6' } + /toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + dev: true + + /toposort@2.0.2: + resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} + requiresBuild: true + dev: false + optional: true + + /totalist@3.0.0: + resolution: {integrity: sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==} + engines: {node: '>=6'} + + /tough-cookie@4.1.3: + resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==} + engines: {node: '>=6'} + dependencies: + psl: 1.9.0 + punycode: 2.2.0 + universalify: 0.2.0 + url-parse: 1.5.10 + dev: true + + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: true + + /tr46@3.0.0: + resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} + engines: {node: '>=12'} + dependencies: + punycode: 2.2.0 + dev: true + + /tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + dev: true + + /trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + dev: false + + /trim-newlines@4.1.1: + resolution: {integrity: sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==} + engines: {node: '>=12'} dev: true - /tough-cookie/2.5.0: - resolution: - { - integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==, - } - engines: { node: '>=0.8' } + /trough@2.1.0: + resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==} + + /ts-algebra@2.0.0: + resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} + requiresBuild: true + dev: false + optional: true + + /ts-api-utils@1.0.2(typescript@5.2.2): + resolution: {integrity: sha512-Cbu4nIqnEdd+THNEsBdkolnOXhg0I8XteoHaEKgvsxpsbWda4IsUut2c187HxywQCvveojow0Dgw/amxtSKVkQ==} + engines: {node: '>=16.13.0'} + peerDependencies: + typescript: '>=4.2.0' dependencies: - psl: 1.8.0 - punycode: 2.1.1 + typescript: 5.2.2 + dev: true + + /ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + dev: true + + /ts-deepmerge@7.0.3: + resolution: {integrity: sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA==} + engines: {node: '>=14.13.1'} + dev: false + + /ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true - /tough-cookie/4.0.0: - resolution: - { - integrity: sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==, - } - engines: { node: '>=6' } + /tsconfig-paths@3.14.2: + resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==} dependencies: - psl: 1.8.0 - punycode: 2.1.1 - universalify: 0.1.2 + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + dev: true + + /tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true - /tr46/0.0.3: - resolution: - { - integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==, - } + /tslib@2.4.0: + resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} + requiresBuild: true + dev: false + optional: true + + /tslib@2.4.1: + resolution: {integrity: sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==} dev: true - /tr46/3.0.0: - resolution: - { - integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==, - } - engines: { node: '>=12' } + /tslib@2.5.0: + resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} + + /tsutils@3.21.0(typescript@5.2.2): + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: - punycode: 2.1.1 + tslib: 1.14.1 + typescript: 5.2.2 dev: true - /tree-kill/1.2.2: - resolution: - { - integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==, - } + /tsx@3.12.3: + resolution: {integrity: sha512-Wc5BFH1xccYTXaQob+lEcimkcb/Pq+0en2s+ruiX0VEIC80nV7/0s7XRahx8NnsoCnpCVUPz8wrqVSPi760LkA==} hasBin: true + dependencies: + '@esbuild-kit/cjs-loader': 2.4.2 + '@esbuild-kit/core-utils': 3.1.0 + '@esbuild-kit/esm-loader': 2.5.5 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /tween-functions@1.2.0: + resolution: {integrity: sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA==} + dev: true + + /type-check@0.3.2: + resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.1.2 + dev: true + + /type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + dev: true + + /type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: true + + /type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + dev: true + + /type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + dev: true + + /type-fest@0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} dev: true - /trim-newlines/3.0.1: - resolution: - { - integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==, - } - engines: { node: '>=8' } + /type-fest@1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} dev: true - /ts-node/10.7.0_2jmrorcux6nsf7sy4ly74hophu: - resolution: - { - integrity: sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==, - } + /type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + + /type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + dev: true + + /typed-array-buffer@1.0.0: + resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + is-typed-array: 1.1.10 + dev: true + + /typed-array-byte-length@1.0.0: + resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + has-proto: 1.0.1 + is-typed-array: 1.1.10 + dev: true + + /typed-array-byte-offset@1.0.0: + resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.7 + for-each: 0.3.3 + has-proto: 1.0.1 + is-typed-array: 1.1.10 + dev: true + + /typed-array-length@1.0.4: + resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + is-typed-array: 1.1.10 + dev: true + + /typedarray-to-buffer@3.1.5: + resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} + dependencies: + is-typedarray: 1.0.0 + dev: true + + /typescript@5.2.2: + resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} + engines: {node: '>=14.17'} hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true + + /unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + dependencies: + call-bind: 1.0.7 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + dev: true + + /unified@10.1.2: + resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} dependencies: - '@cspotcode/source-map-support': 0.7.0 - '@tsconfig/node10': 1.0.8 - '@tsconfig/node12': 1.0.9 - '@tsconfig/node14': 1.0.1 - '@tsconfig/node16': 1.0.2 - '@types/node': 16.11.39 - acorn: 8.7.1 - acorn-walk: 8.2.0 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 4.6.4 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - dev: true - - /ts-poet/4.11.0: - resolution: - { - integrity: sha512-OaXnCKsRs0yrc0O7LFhnq/US2DB4Wd313cS+qjG2XMksZ74pF/jvMHkJdURXJiAo4kSahL2N4e8JOdwUjOMNdw==, - } + '@types/unist': 2.0.8 + bail: 2.0.2 + extend: 3.0.2 + is-buffer: 2.0.5 + is-plain-obj: 4.1.0 + trough: 2.1.0 + vfile: 5.3.7 + + /unionfs@4.5.1: + resolution: {integrity: sha512-hn8pzkh0/80mpsIT/YBJKa4+BF/9pNh0IgysBi0CjL95Uok8Hus69TNfgeJckoUNwfTpBq26+F7edO1oBINaIw==} dependencies: - lodash: 4.17.21 - prettier: 2.7.1 + fs-monkey: 1.0.3 dev: true - /ts-proto-descriptors/1.6.0: - resolution: - { - integrity: sha512-Vrhue2Ti99us/o76mGy28nF3W/Uanl1/8detyJw2yyRwiBC5yxy+hEZqQ/ZX2PbZ1vyCpJ51A9L4PnCCnkBMTQ==, - } + /unist-util-is@5.2.1: + resolution: {integrity: sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==} dependencies: - long: 4.0.0 - protobufjs: 6.11.3 - dev: true + '@types/unist': 2.0.8 - /ts-proto/1.112.2: - resolution: - { - integrity: sha512-+97ks5l+R49nzY/sN3Z43+55suW2+URZt1LoXC+EIg6tmeV0556JqwNp+vVcdXJgUG/gXoypYbOiB0m+RFf9Qg==, - } - hasBin: true + /unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} dependencies: - '@types/object-hash': 1.3.4 - dataloader: 1.4.0 - object-hash: 1.3.1 - protobufjs: 6.11.2 - ts-poet: 4.11.0 - ts-proto-descriptors: 1.6.0 - dev: true + '@types/unist': 3.0.0 - /tslib/1.14.1: - resolution: - { - integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==, - } + /unist-util-position@4.0.4: + resolution: {integrity: sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==} + dependencies: + '@types/unist': 2.0.8 dev: true - /tslib/2.4.0: - resolution: - { - integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==, - } - dev: true + /unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + dependencies: + '@types/unist': 3.0.0 + dev: false - /tsutils/3.21.0_typescript@4.6.4: - resolution: - { - integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==, - } - engines: { node: '>= 6' } - peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + /unist-util-remove@4.0.0: + resolution: {integrity: sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==} dependencies: - tslib: 1.14.1 - typescript: 4.6.4 - dev: true + '@types/unist': 3.0.0 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + dev: false - /tunnel-agent/0.6.0: - resolution: - { - integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==, - } + /unist-util-stringify-position@3.0.3: + resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==} dependencies: - safe-buffer: 5.1.2 - dev: true + '@types/unist': 2.0.8 - /tweetnacl/0.14.5: - resolution: - { - integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==, - } - dev: true + /unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + dependencies: + '@types/unist': 3.0.0 + dev: false - /type-check/0.3.2: - resolution: - { - integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==, - } - engines: { node: '>= 0.8.0' } + /unist-util-visit-parents@5.1.3: + resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==} dependencies: - prelude-ls: 1.1.2 - dev: true + '@types/unist': 2.0.8 + unist-util-is: 5.2.1 - /type-check/0.4.0: - resolution: - { - integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, - } - engines: { node: '>= 0.8.0' } + /unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} dependencies: - prelude-ls: 1.2.1 - dev: true + '@types/unist': 3.0.0 + unist-util-is: 6.0.0 - /type-detect/4.0.8: - resolution: - { - integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==, - } - engines: { node: '>=4' } - dev: true + /unist-util-visit@4.1.2: + resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==} + dependencies: + '@types/unist': 2.0.8 + unist-util-is: 5.2.1 + unist-util-visit-parents: 5.1.3 - /type-fest/0.18.1: - resolution: - { - integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==, - } - engines: { node: '>=10' } - dev: true + /unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + dependencies: + '@types/unist': 3.0.0 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 - /type-fest/0.20.2: - resolution: - { - integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==, - } - engines: { node: '>=10' } + /universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} dev: true - /type-fest/0.21.3: - resolution: - { - integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==, - } - engines: { node: '>=10' } + /universalify@2.0.0: + resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} + engines: {node: '>= 10.0.0'} dev: true - /type-fest/0.6.0: - resolution: - { - integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==, - } - engines: { node: '>=8' } + /unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} dev: true - /type-fest/0.8.1: - resolution: - { - integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==, - } - engines: { node: '>=8' } + /unplugin@1.5.0: + resolution: {integrity: sha512-9ZdRwbh/4gcm1JTOkp9lAkIDrtOyOxgHmY7cjuwI8L/2RTikMcVG25GsZwNAgRuap3iDw2jeq7eoqtAsz5rW3A==} + dependencies: + acorn: 8.14.0 + chokidar: 3.5.3 + webpack-sources: 3.2.3 + webpack-virtual-modules: 0.5.0 dev: true - /typedarray/0.0.6: - resolution: - { - integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==, - } + /update-browserslist-db@1.0.13(browserslist@4.21.4): + resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + dependencies: + browserslist: 4.21.4 + escalade: 3.1.1 + picocolors: 1.1.1 dev: true - /typescript/4.6.4: - resolution: - { - integrity: sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==, - } - engines: { node: '>=4.2.0' } + /update-browserslist-db@1.0.13(browserslist@4.23.0): + resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + dependencies: + browserslist: 4.23.0 + escalade: 3.1.1 + picocolors: 1.1.1 dev: true - /uc.micro/1.0.6: - resolution: - { - integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==, - } + /uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.2.0 dev: true - /ufo/0.8.5: - resolution: - { - integrity: sha512-e4+UtA5IRO+ha6hYklwj6r7BjiGMxS0O+UaSg9HbaTefg4kMkzj4tXzEBajRR+wkxf+golgAWKzLbytCUDMJAA==, - } + /url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 dev: true - /unbox-primitive/1.0.2: - resolution: - { - integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==, - } - dependencies: - call-bind: 1.0.2 - has-bigints: 1.0.2 - has-symbols: 1.0.3 - which-boxed-primitive: 1.0.2 + /url-pattern@1.0.3: + resolution: {integrity: sha512-uQcEj/2puA4aq1R3A2+VNVBgaWYR24FdWjl7VNW83rnWftlhyzOZ/tBjezRiC2UkIzuxC8Top3IekN3vUf1WxA==} + engines: {node: '>=0.12.0'} dev: false - /universalify/0.1.2: - resolution: - { - integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==, - } - engines: { node: '>= 4.0.0' } + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: true + + /util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + dependencies: + inherits: 2.0.4 + is-arguments: 1.1.1 + is-generator-function: 1.0.10 + is-typed-array: 1.1.10 + which-typed-array: 1.1.11 dev: true - /universalify/2.0.0: - resolution: - { - integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==, - } - engines: { node: '>= 10.0.0' } + /utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} dev: true - /unpipe/1.0.0: - resolution: - { - integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==, - } - engines: { node: '>= 0.8' } + /uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true dev: true - /untildify/4.0.0: - resolution: - { - integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==, - } - engines: { node: '>=8' } + /uuid@9.0.0: + resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} + hasBin: true + dev: false + + /uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true dev: true - /upper-case-first/2.0.2: - resolution: - { - integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==, - } + /uvu@0.5.6: + resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} + engines: {node: '>=8'} + hasBin: true dependencies: - tslib: 2.4.0 + dequal: 2.0.3 + diff: 5.1.0 + kleur: 4.1.5 + sade: 1.8.1 dev: true - /upper-case/2.0.2: - resolution: - { - integrity: sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==, - } - dependencies: - tslib: 2.4.0 + /v8-compile-cache@2.4.0: + resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==} dev: true - /uri-js/4.4.1: - resolution: - { - integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, - } + /v8-to-istanbul@9.0.1: + resolution: {integrity: sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==} + engines: {node: '>=10.12.0'} dependencies: - punycode: 2.1.1 + '@jridgewell/trace-mapping': 0.3.25 + '@types/istanbul-lib-coverage': 2.0.4 + convert-source-map: 1.9.0 dev: true - /url-pattern/1.0.3: - resolution: { integrity: sha1-BAkpJHGyTyPFDWWkeTF5PStaz8E= } - engines: { node: '>=0.12.0' } + /valibot@0.42.1(typescript@5.2.2): + resolution: {integrity: sha512-3keXV29Ar5b//Hqi4MbSdV7lfVp6zuYLZuA9V1PvQUsXqogr+u5lvLPLk3A4f74VUXDnf/JfWMN6sB+koJ/FFw==} + requiresBuild: true + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + typescript: 5.2.2 dev: false + optional: true - /util-deprecate/1.0.2: - resolution: - { - integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, - } - dev: true + /valibot@1.1.0(typescript@5.2.2): + resolution: {integrity: sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==} + requiresBuild: true + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + typescript: 5.2.2 + dev: false + optional: true - /utils-merge/1.0.1: - resolution: - { - integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==, - } - engines: { node: '>= 0.4.0' } + /validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + dependencies: + spdx-correct: 3.1.1 + spdx-expression-parse: 3.0.1 dev: true - /uuid/8.3.2: - resolution: - { - integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==, - } - hasBin: true + /validator@13.15.15: + resolution: {integrity: sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==} + engines: {node: '>= 0.10'} + requiresBuild: true + dev: false + optional: true - /v8-compile-cache-lib/3.0.1: - resolution: - { - integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==, - } + /vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} dev: true - /v8-compile-cache/2.3.0: - resolution: - { - integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==, - } + /vfile-location@4.1.0: + resolution: {integrity: sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==} + dependencies: + '@types/unist': 2.0.8 + vfile: 5.3.7 dev: true - /v8-to-istanbul/9.0.1: - resolution: - { - integrity: sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==, - } - engines: { node: '>=10.12.0' } + /vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} dependencies: - '@jridgewell/trace-mapping': 0.3.13 - '@types/istanbul-lib-coverage': 2.0.4 - convert-source-map: 1.8.0 - dev: true + '@types/unist': 3.0.0 + vfile: 6.0.1 + dev: false - /validate-npm-package-license/3.0.4: - resolution: - { - integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==, - } + /vfile-message@3.1.4: + resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} dependencies: - spdx-correct: 3.1.1 - spdx-expression-parse: 3.0.1 - dev: true + '@types/unist': 2.0.8 + unist-util-stringify-position: 3.0.3 - /verror/1.10.0: - resolution: - { - integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==, - } - engines: { '0': node >=0.6.0 } + /vfile-message@4.0.2: + resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} dependencies: - assert-plus: 1.0.0 - core-util-is: 1.0.2 - extsprintf: 1.3.0 - dev: true + '@types/unist': 3.0.0 + unist-util-stringify-position: 4.0.0 + dev: false - /vite-node/0.23.2: - resolution: - { - integrity: sha512-LRkcAu0oQYNp+2LX84Z8NkYXzISdeLi9NxL8cWgJPxxYcv8Lv9VpqU4kNzAqTIWzgIQFnVkf5mZarcoLgQXInQ==, - } - engines: { node: '>=v14.16.0' } - hasBin: true + /vfile@5.3.7: + resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==} dependencies: - debug: 4.3.4 - mlly: 0.5.14 - pathe: 0.2.0 - vite: 3.1.0 - transitivePeerDependencies: - - less - - sass - - stylus - - supports-color - - terser - dev: true + '@types/unist': 2.0.8 + is-buffer: 2.0.5 + unist-util-stringify-position: 3.0.3 + vfile-message: 3.1.4 + + /vfile@6.0.1: + resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} + dependencies: + '@types/unist': 3.0.0 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.2 + dev: false - /vite-node/0.23.4: - resolution: - { - integrity: sha512-8VuDGwTWIvwPYcbw8ZycMlwAwqCmqZfLdFrDK75+o+6bWYpede58k6AAXN9ioU+icW82V4u1MzkxLVhhIoQ9xA==, - } - engines: { node: '>=v14.16.0' } + /vite-node@3.1.1(@types/node@18.15.3): + resolution: {integrity: sha512-V+IxPAE2FvXpTCHXyNem0M+gWm6J7eRyWPR6vYoG/Gl+IscNOjXzztUhimQgTxaAoUoj40Qqimaa0NLIOOAH4w==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true dependencies: - debug: 4.3.4 - mlly: 0.5.14 - pathe: 0.2.0 - vite: 3.1.0 + cac: 6.7.14 + debug: 4.4.0 + es-module-lexer: 1.6.0 + pathe: 2.0.3 + vite: 6.2.7(@types/node@18.15.3) transitivePeerDependencies: + - '@types/node' + - jiti - less + - lightningcss - sass + - sass-embedded - stylus + - sugarss - supports-color - terser + - tsx + - yaml dev: true - /vite-plugin-inspect/0.7.5_vite@3.1.0: - resolution: - { - integrity: sha512-O8xq6dbvD1G7le75u6BfQTXJDUMXcRYlMVPzUpRjSB6hLyqH6BNnNkNrb/cVgCNNmKwk7dX+H2I+XVU2xpJk9Q==, - } - engines: { node: '>=14' } - peerDependencies: - vite: ^3.1.0 - dependencies: - '@rollup/pluginutils': 4.2.1 - debug: 4.3.4 - fs-extra: 10.1.0 - kolorist: 1.6.0 - sirv: 2.0.2 - ufo: 0.8.5 - vite: 3.1.0 - transitivePeerDependencies: - - supports-color - dev: true - - /vite/2.9.12: - resolution: - { - integrity: sha512-suxC36dQo9Rq1qMB2qiRorNJtJAdxguu5TMvBHOc/F370KvqAe9t48vYp+/TbPKRNrMh/J55tOUmkuIqstZaew==, - } - engines: { node: '>=12.2.0' } + /vite@6.2.7(@types/node@18.15.3): + resolution: {integrity: sha512-qg3LkeuinTrZoJHHF94coSaTfIPyBYoywp+ys4qu20oSJFbKMYoIJo0FWJT9q6Vp49l6z9IsJRbHdcGtiKbGoQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' less: '*' + lightningcss: ^1.21.0 sass: '*' + sass-embedded: '*' stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true less: optional: true + lightningcss: + optional: true sass: optional: true + sass-embedded: + optional: true stylus: optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true dependencies: - esbuild: 0.14.54 - postcss: 8.4.16 - resolve: 1.22.0 - rollup: 2.73.0 + '@types/node': 18.15.3 + esbuild: 0.25.0 + postcss: 8.5.3 + rollup: 4.40.1 optionalDependencies: - fsevents: 2.3.2 - dev: true + fsevents: 2.3.3 - /vite/3.1.0: - resolution: - { - integrity: sha512-YBg3dUicDpDWFCGttmvMbVyS9ydjntwEjwXRj2KBFwSB8SxmGcudo1yb8FW5+M/G86aS8x828ujnzUVdsLjs9g==, - } - engines: { node: ^14.18.0 || >=16.0.0 } - hasBin: true + /vitefu@1.0.6(vite@6.2.7): + resolution: {integrity: sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==} peerDependencies: - less: '*' - sass: '*' - stylus: '*' - terser: ^5.4.0 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 peerDependenciesMeta: - less: - optional: true - sass: - optional: true - stylus: - optional: true - terser: + vite: optional: true dependencies: - esbuild: 0.15.7 - postcss: 8.4.16 - resolve: 1.22.1 - rollup: 2.78.1 - optionalDependencies: - fsevents: 2.3.2 + vite: 6.2.7(@types/node@18.15.3) + + /vitest-localstorage-mock@0.1.2(vitest@3.1.1): + resolution: {integrity: sha512-1oee6iDWhhquzVogssbpwQi6a2F3L+nCKF2+qqyCs5tH0sOYRyTqnsfj2dtmEQiL4xtJkHLn42hEjHGESlsJHw==} + peerDependencies: + vitest: '*' + dependencies: + vitest: 3.1.1(@types/node@18.15.3)(@vitest/ui@3.1.1)(jsdom@20.0.3) dev: true - /vitest/0.15.2_dcowuwleuujrodfztzblsok4y4: - resolution: - { - integrity: sha512-cMabuUqu+nNHafkdN7H8Z20+UZTrrUfqjGwAoLwUwrqFGWBz3gXwxndjbLf6mgSFs9lF/JWjKeNM1CXKwtk26w==, - } - engines: { node: '>=v14.16.0' } + /vitest@3.1.1(@types/node@18.15.3)(@vitest/ui@3.1.1)(jsdom@20.0.3): + resolution: {integrity: sha512-kiZc/IYmKICeBAZr9DQ5rT7/6bD9G7uqQEki4fxazi1jdVl2mWGzedtBs5s6llz59yQhVb7FFY2MbHzHCnT79Q==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: - '@vitest/ui': '*' - c8: '*' + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.1.1 + '@vitest/ui': 3.1.1 happy-dom: '*' jsdom: '*' peerDependenciesMeta: - '@vitest/ui': + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': optional: true - c8: + '@vitest/browser': + optional: true + '@vitest/ui': optional: true happy-dom: optional: true jsdom: optional: true dependencies: - '@types/chai': 4.3.1 - '@types/chai-subset': 1.3.3 - '@types/node': 17.0.41 - '@vitest/ui': 0.16.0 - c8: 7.11.3 - chai: 4.3.6 - debug: 4.3.4 - jsdom: 20.0.0 - local-pkg: 0.4.1 - tinypool: 0.1.3 - tinyspy: 0.3.3 - vite: 2.9.12 + '@types/node': 18.15.3 + '@vitest/expect': 3.1.1 + '@vitest/mocker': 3.1.1(vite@6.2.7) + '@vitest/pretty-format': 3.1.1 + '@vitest/runner': 3.1.1 + '@vitest/snapshot': 3.1.1 + '@vitest/spy': 3.1.1 + '@vitest/ui': 3.1.1(vitest@3.1.1) + '@vitest/utils': 3.1.1 + chai: 5.2.0 + debug: 4.4.0 + expect-type: 1.2.0 + jsdom: 20.0.3 + magic-string: 0.30.17 + pathe: 2.0.3 + std-env: 3.8.1 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.0.2 + tinyrainbow: 2.0.0 + vite: 6.2.7(@types/node@18.15.3) + vite-node: 3.1.1(@types/node@18.15.3) + why-is-node-running: 2.3.0 transitivePeerDependencies: + - jiti - less + - lightningcss + - msw - sass + - sass-embedded - stylus + - sugarss - supports-color + - terser + - tsx + - yaml dev: true - /vscode-oniguruma/1.6.2: - resolution: - { - integrity: sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==, - } + /w3c-keyname@2.2.6: + resolution: {integrity: sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==} + dev: false + + /w3c-xmlserializer@4.0.0: + resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} + engines: {node: '>=14'} + dependencies: + xml-name-validator: 4.0.0 dev: true - /vscode-textmate/5.2.0: - resolution: - { - integrity: sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==, - } + /wait-on@7.2.0: + resolution: {integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==} + engines: {node: '>=12.0.0'} + hasBin: true + dependencies: + axios: 1.7.4 + joi: 17.13.1 + lodash: 4.17.21 + minimist: 1.2.8 + rxjs: 7.8.1 + transitivePeerDependencies: + - debug dev: true - /w3c-hr-time/1.0.2: - resolution: - { - integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==, - } + /wait-port@0.2.14: + resolution: {integrity: sha512-kIzjWcr6ykl7WFbZd0TMae8xovwqcqbx6FM9l+7agOgUByhzdjfzZBPK2CPufldTOMxbUivss//Sh9MFawmPRQ==} + engines: {node: '>=8'} + hasBin: true dependencies: - browser-process-hrtime: 1.0.0 + chalk: 2.4.2 + commander: 3.0.2 + debug: 4.4.0 + transitivePeerDependencies: + - supports-color dev: true - /w3c-keyname/2.2.6: - resolution: - { - integrity: sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==, - } + /wait-port@1.0.4: + resolution: {integrity: sha512-w8Ftna3h6XSFWWc2JC5gZEgp64nz8bnaTp5cvzbJSZ53j+omktWTDdwXxEF0jM8YveviLgFWvNGrSvRHnkyHyw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + chalk: 4.1.2 + commander: 9.5.0 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color dev: true - /w3c-xmlserializer/3.0.0: - resolution: - { - integrity: sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==, - } - engines: { node: '>=12' } + /walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} dependencies: - xml-name-validator: 4.0.0 + makeerror: 1.0.12 dev: true - /watchpack/2.4.0: - resolution: - { - integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==, - } - engines: { node: '>=10.13.0' } + /watchpack@2.4.2: + resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==} + engines: {node: '>=10.13.0'} dependencies: glob-to-regexp: 0.4.1 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 + dev: true + + /web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + + /web-streams-polyfill@3.2.1: + resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} + engines: {node: '>= 8'} + dev: true + + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: true - /webidl-conversions/3.0.1: - resolution: - { - integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==, - } + /webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} dev: true - /webidl-conversions/7.0.0: - resolution: - { - integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==, - } - engines: { node: '>=12' } + /webpack-sources@3.2.3: + resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} + engines: {node: '>=10.13.0'} dev: true - /webpack-sources/3.2.3: - resolution: - { - integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==, - } - engines: { node: '>=10.13.0' } + /webpack-virtual-modules@0.5.0: + resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==} dev: true - /webpack/5.73.0_esbuild@0.13.15: - resolution: - { - integrity: sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA==, - } - engines: { node: '>=10.13.0' } + /webpack@5.94.0(@swc/core@1.10.7)(esbuild@0.25.0): + resolution: {integrity: sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==} + engines: {node: '>=10.13.0'} hasBin: true peerDependencies: webpack-cli: '*' @@ -10785,29 +13955,28 @@ packages: webpack-cli: optional: true dependencies: - '@types/eslint-scope': 3.7.3 - '@types/estree': 0.0.51 - '@webassemblyjs/ast': 1.11.1 - '@webassemblyjs/wasm-edit': 1.11.1 - '@webassemblyjs/wasm-parser': 1.11.1 - acorn: 8.7.1 - acorn-import-assertions: 1.8.0_acorn@8.7.1 - browserslist: 4.20.3 + '@types/estree': 1.0.6 + '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/wasm-edit': 1.12.1 + '@webassemblyjs/wasm-parser': 1.12.1 + acorn: 8.14.0 + acorn-import-attributes: 1.9.5(acorn@8.14.0) + browserslist: 4.23.0 chrome-trace-event: 1.0.3 - enhanced-resolve: 5.9.3 - es-module-lexer: 0.9.3 + enhanced-resolve: 5.17.1 + es-module-lexer: 1.6.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 json-parse-even-better-errors: 2.3.1 loader-runner: 4.3.0 mime-types: 2.1.35 neo-async: 2.6.2 - schema-utils: 3.1.1 + schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.3_7cpexqm4fdhvdbn47hablhluku - watchpack: 2.4.0 + terser-webpack-plugin: 5.3.10(@swc/core@1.10.7)(esbuild@0.25.0)(webpack@5.94.0) + watchpack: 2.4.2 webpack-sources: 3.2.3 transitivePeerDependencies: - '@swc/core' @@ -10815,160 +13984,163 @@ packages: - uglify-js dev: true - /websocket-as-promised/2.0.1: - resolution: - { - integrity: sha512-ePV26D/D37ughXU9j+DjGmwUbelWJrC/vi+6GK++fRlBJmS7aU9T8ABu47KFF0O7r6XN2NAuqJRpegbUwXZxQg==, - } - engines: { node: '>=6' } - dependencies: - chnl: 1.2.0 - promise-controller: 1.0.0 - promise.prototype.finally: 3.1.3 - promised-map: 1.0.0 - dev: false - - /whatwg-encoding/2.0.0: - resolution: - { - integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==, - } - engines: { node: '>=12' } + /whatwg-encoding@2.0.0: + resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} + engines: {node: '>=12'} dependencies: iconv-lite: 0.6.3 dev: true - /whatwg-mimetype/3.0.0: - resolution: - { - integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==, - } - engines: { node: '>=12' } + /whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} dev: true - /whatwg-url/11.0.0: - resolution: - { - integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==, - } - engines: { node: '>=12' } + /whatwg-url@11.0.0: + resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} + engines: {node: '>=12'} dependencies: tr46: 3.0.0 webidl-conversions: 7.0.0 dev: true - /whatwg-url/5.0.0: - resolution: - { - integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==, - } + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 dev: true - /which-boxed-primitive/1.0.2: - resolution: - { - integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==, - } + /which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: is-bigint: 1.0.4 is-boolean-object: 1.1.2 is-number-object: 1.0.7 is-string: 1.0.7 is-symbol: 1.0.4 - dev: false + dev: true + + /which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + dev: true - /which/1.3.1: - resolution: - { - integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==, - } + /which-typed-array@1.1.11: + resolution: {integrity: sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.0 + dev: true + + /which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true dependencies: isexe: 2.0.0 dev: true - /which/2.0.2: - resolution: - { - integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, - } - engines: { node: '>= 8' } + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} hasBin: true dependencies: isexe: 2.0.0 dev: true - /wide-align/1.1.5: - resolution: - { - integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==, - } + /which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true + dependencies: + isexe: 3.1.1 + dev: true + + /why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: true + + /wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} dependencies: string-width: 4.2.3 dev: true - /word-wrap/1.2.3: - resolution: - { - integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==, - } - engines: { node: '>=0.10.0' } + /word-wrap@1.2.4: + resolution: {integrity: sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==} + engines: {node: '>=0.10.0'} dev: true - /wrap-ansi/6.2.0: - resolution: - { - integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==, - } - engines: { node: '>=8' } + /wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 dev: true - /wrap-ansi/7.0.0: - resolution: - { - integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==, - } - engines: { node: '>=10' } + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 dev: true - /wrappy/1.0.2: - resolution: - { - integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==, - } + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + dev: true + + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: true - /write-file-atomic/4.0.1: - resolution: - { - integrity: sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ==, - } - engines: { node: ^12.13.0 || ^14.15.0 || >=16 } + /write-file-atomic@3.0.3: + resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} + dependencies: + imurmurhash: 0.1.4 + is-typedarray: 1.0.0 + signal-exit: 3.0.7 + typedarray-to-buffer: 3.1.5 + dev: true + + /write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} dependencies: imurmurhash: 0.1.4 signal-exit: 3.0.7 dev: true - /ws/8.8.0: - resolution: - { - integrity: sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==, - } - engines: { node: '>=10.0.0' } + /write-file-atomic@5.0.1: + resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + dev: true + + /ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 + utf-8-validate: '>=5.0.2' peerDependenciesMeta: bufferutil: optional: true @@ -10976,82 +14148,94 @@ packages: optional: true dev: true - /xml-name-validator/4.0.0: - resolution: - { - integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==, - } - engines: { node: '>=12' } + /xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + dev: true + + /xml@1.0.1: + resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==} + dev: true + + /xmlbuilder@15.1.1: + resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} + engines: {node: '>=8.0'} + dev: true + + /xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} dev: true - /xmlchars/2.2.0: - resolution: - { - integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==, - } + /y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} dev: true - /xtend/4.0.2: - resolution: - { - integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==, - } - engines: { node: '>=0.4' } + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} dev: true - /y18n/5.0.8: - resolution: - { - integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==, - } - engines: { node: '>=10' } + /yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} dev: true - /yallist/4.0.0: - resolution: - { - integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==, - } + /yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true - /yaml/1.10.2: - resolution: - { - integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==, - } - engines: { node: '>= 6' } + /yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + dev: true + + /yaml@2.2.1: + resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==} + engines: {node: '>= 14'} + dev: true + + /yaml@2.3.4: + resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} + engines: {node: '>= 14'} + dev: true + + /yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 dev: true - /yaml/2.1.1: - resolution: - { - integrity: sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==, - } - engines: { node: '>= 14' } + /yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} dev: true - /yargs-parser/20.2.9: - resolution: - { - integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==, - } - engines: { node: '>=10' } + /yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} dev: true - /yargs-parser/21.0.1: - resolution: - { - integrity: sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==, - } - engines: { node: '>=12' } + /yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 dev: true - /yargs/16.2.0: - resolution: - { - integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==, - } - engines: { node: '>=10' } + /yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} dependencies: cliui: 7.0.4 escalade: 3.1.1 @@ -11062,44 +14246,71 @@ packages: yargs-parser: 20.2.9 dev: true - /yargs/17.5.1: - resolution: - { - integrity: sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==, - } - engines: { node: '>=12' } + /yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} dependencies: - cliui: 7.0.4 + cliui: 8.0.1 escalade: 3.1.1 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 y18n: 5.0.8 - yargs-parser: 21.0.1 + yargs-parser: 21.1.1 dev: true - /yauzl/2.10.0: - resolution: - { - integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==, - } - dependencies: - buffer-crc32: 0.2.13 - fd-slicer: 1.1.0 + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} dev: true - /yn/3.1.1: - resolution: - { - integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==, - } - engines: { node: '>=6' } - dev: true + /yup@1.6.1: + resolution: {integrity: sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA==} + requiresBuild: true + dependencies: + property-expr: 2.0.6 + tiny-case: 1.0.3 + toposort: 2.0.2 + type-fest: 2.19.0 + dev: false + optional: true - /yocto-queue/0.1.0: - resolution: - { - integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, - } - engines: { node: '>=10' } + /zimmerframe@1.1.2: + resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} + + /zod-to-json-schema@3.24.6(zod@3.25.67): + resolution: {integrity: sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==} + requiresBuild: true + peerDependencies: + zod: ^3.24.1 + dependencies: + zod: 3.25.67 + dev: false + optional: true + + /zod@3.25.67: + resolution: {integrity: sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==} + dev: false + + /zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + + /zx@7.1.1: + resolution: {integrity: sha512-5YlTO2AJ+Ku2YuZKSSSqnUKuagcM/f/j4LmHs15O84Ch80Z9gzR09ZK3gR7GV+rc8IFpz2H/XNFtFVmj31yrZA==} + engines: {node: '>= 16.0.0'} + hasBin: true + dependencies: + '@types/fs-extra': 9.0.13 + '@types/minimist': 1.2.2 + '@types/node': 18.15.3 + '@types/ps-tree': 1.1.2 + '@types/which': 2.0.1 + chalk: 5.2.0 + fs-extra: 10.1.0 + globby: 13.1.3 + minimist: 1.2.7 + node-fetch: 3.2.10 + ps-tree: 1.2.0 + which: 2.0.2 + yaml: 2.2.1 dev: true diff --git a/scripts/audit-tailwind-colors/index.ts b/scripts/audit-tailwind-colors/index.ts new file mode 100644 index 000000000..13946b331 --- /dev/null +++ b/scripts/audit-tailwind-colors/index.ts @@ -0,0 +1,59 @@ +import { mkdir, readFile, writeFile } from 'fs/promises'; +import { dirname, join } from 'path'; + +import glob from 'fast-glob'; + +import { parseTailwindClass } from './parse-tailwind-class'; +import { toRows } from './to-rows'; +import type { Result } from './types'; +import { isDirectory, pattern, src } from './utilities'; +import { getProjectRoot } from '../get-project-root'; + +const findTailwindColors = async (directory = src) => { + const results: Result[] = []; + const files = glob.sync(join(directory, '**', '*.svelte')); + + for (const file of files) { + const content = await readFile(file, 'utf-8'); + const lines = content.split('\n'); + + let index = 1; + + for (const line of lines) { + let match: RegExpExecArray | null; + + while ((match = pattern.exec(line)) !== null) { + const result = { + path: file.replace(src, 'src'), + line: index, + class: match[0], + }; + + results.push({ ...result, ...parseTailwindClass(result.class) }); + } + + index++; + } + } + + return results; +}; + +const saveToCsv = async ( + results: Result[], + fileName = join(getProjectRoot(), 'audits', 'tailwind-colors.csv'), +) => { + const exists = await isDirectory(dirname(fileName)); + + if (!exists) await mkdir(dirname(fileName)); + + await writeFile(fileName, toRows(results)); + + console.log( + `${results.length} Tailwind color instances found and saved to '${fileName}'.`, + ); +}; + +const results = await findTailwindColors(src); + +saveToCsv(results); diff --git a/scripts/audit-tailwind-colors/parse-tailwind-class.ts b/scripts/audit-tailwind-colors/parse-tailwind-class.ts new file mode 100644 index 000000000..102fa87a7 --- /dev/null +++ b/scripts/audit-tailwind-colors/parse-tailwind-class.ts @@ -0,0 +1,42 @@ +import type { TailwindClass } from './types'; + +export const parseTailwindClass = (tailwindClass: string): TailwindClass => { + const result: TailwindClass = { + variant: null, + utility: null, + color: null, + shade: null, + }; + + // Match the variant prefix, if present + const variantMatch = tailwindClass.match( + /^(hover|focus|active|group-hover|focus-within):/, + ); + if (variantMatch) { + result.variant = variantMatch[1]; + tailwindClass = tailwindClass.replace(variantMatch[0], ''); + } + + // Match the utility + const utilityMatch = tailwindClass.match( + /^(text|bg|border|divide|ring|placeholder)-/, + ); + if (utilityMatch) { + result.utility = utilityMatch[1]; + tailwindClass = tailwindClass.replace(utilityMatch[0], ''); + } + + // Match the color + const colorMatch = tailwindClass.match(/^(\w+)-/); + if (colorMatch) { + result.color = colorMatch[1]; + tailwindClass = tailwindClass.replace(colorMatch[0], ''); + } + + // Whatever is left must be the shade + if (tailwindClass) { + result.shade = tailwindClass; + } + + return result; +}; diff --git a/scripts/audit-tailwind-colors/to-rows.ts b/scripts/audit-tailwind-colors/to-rows.ts new file mode 100644 index 000000000..3dcb01cb0 --- /dev/null +++ b/scripts/audit-tailwind-colors/to-rows.ts @@ -0,0 +1,20 @@ +import { type Result } from './types'; +import { getProjectRoot } from '../get-project-root'; + +export const toRow = (result: Result) => { + return [ + result.path.replace(getProjectRoot(), ''), + result.line, + result.class, + result.variant || '', + result.utility || '', + result.color || '', + result.shade || '', + ].join(','); +}; + +export const toRows = (results: Result[]) => { + return results.reduce((rows, result) => { + return rows + toRow(result) + '\n'; + }, 'File,Line Number,Class,Variant,Utility,Color,Shade\n'); +}; diff --git a/scripts/audit-tailwind-colors/types.ts b/scripts/audit-tailwind-colors/types.ts new file mode 100644 index 000000000..09cf8d56f --- /dev/null +++ b/scripts/audit-tailwind-colors/types.ts @@ -0,0 +1,12 @@ +export type TailwindClass = { + variant: string | null; + utility: string | null; + color: string | null; + shade: string | null; +}; + +export type Result = { + path: string; + line: number; + class: string; +} & TailwindClass; diff --git a/scripts/audit-tailwind-colors/utilities.ts b/scripts/audit-tailwind-colors/utilities.ts new file mode 100644 index 000000000..b9caabeb9 --- /dev/null +++ b/scripts/audit-tailwind-colors/utilities.ts @@ -0,0 +1,70 @@ +import { readdir, stat } from 'fs/promises'; +import { join, resolve } from 'path'; + +import colors from '../../src/theme/colors'; +import { getProjectRoot } from '../get-project-root'; + +export type FileExtension = `.${string}`; + +const temporalColors = Object.keys(colors).join('|'); + +const variants = ['hover', 'focus', 'active', 'group-hover', 'focus-within'] + .map((v) => `${v}:`) + .join('|'); + +const utilities = [ + 'text', + 'bg', + 'border', + 'divide', + 'ring', + 'placeholder', +].join('|'); + +export const pattern = new RegExp( + `\\b(?:${variants})?(?:${utilities})-(?:${temporalColors})-(\\d{1,3})\\b`, + 'g', +); + +export const fileExentsions: FileExtension[] = ['.svelte', '.ts']; + +export const src = resolve(getProjectRoot(), 'src'); + +export const isDirectory = async (path: string) => { + try { + return (await stat(path)).isDirectory(); + } catch { + return false; + } +}; + +export const hasExtension = ( + path: string, + extensions: FileExtension | FileExtension[] = fileExentsions, +) => { + if (typeof extensions === 'string') { + extensions = [extensions]; + } + + return extensions.some((extension) => path.endsWith(extension)); +}; + +export const findFiles = async (directory = src) => { + let filePaths: string[] = []; + + for (const fileName of await readdir(directory)) { + const fullPath = join(directory, fileName); + const pathIsDirectory = await isDirectory(fullPath); + + if (pathIsDirectory) { + const results = await findFiles(fullPath); + filePaths = filePaths.concat(results); + } + + if (hasExtension(fullPath)) { + filePaths.push(fullPath); + } + } + + return filePaths; +}; diff --git a/scripts/download-temporal.ts b/scripts/download-temporal.ts new file mode 100644 index 000000000..7d5d94508 --- /dev/null +++ b/scripts/download-temporal.ts @@ -0,0 +1,82 @@ +import { chmod } from 'fs/promises'; +import zlib from 'node:zlib'; +import { join } from 'path'; +import { finished } from 'stream/promises'; + +import mkdirp from 'mkdirp'; +import fetch from 'node-fetch'; +import rmrf from 'rimraf'; +import tar from 'tar-fs'; +import { chalk } from 'zx'; + +console.log(chalk.cyan('Getting ready to download Temporal CLI…')); + +const { rimraf } = rmrf; + +if (process.env.VERCEL) { + console.log( + chalk.blue('Running on Vercel; skipping downloading Temporal CLI.'), + ); + process.exit(0); +} + +const reportError = (error: string, exitCode = 1, callback?: () => void) => { + console.error(chalk.bgRed('Error:'), chalk.red(error)); + if (callback && typeof callback === 'function') { + callback(); + } + process.exit(exitCode); +}; + +const removeDirectory = async () => { + await rimraf(destination); +}; + +const destinationDirectory = './bin'; +const destination = join(destinationDirectory, 'cli'); + +const platform = process.platform; +let arch = process.arch; + +if (arch === 'x64') arch = 'amd64'; + +const downloadUrl = `https://temporal.download/cli/archive/latest?platform=${platform}&arch=${arch}`; + +removeDirectory(); +await mkdirp(destinationDirectory); + +console.log( + chalk.bgYellow('Downloading:'), + chalk.yellow.underline(downloadUrl), +); + +try { + const response = await fetch(downloadUrl); + + if (!response.ok) { + console.warn( + chalk.magenta.bold('There was an error fetching Temporal CLI'), + chalk.bgRed.white.bold(` ${response.status} (${response.statusText}) `), + chalk.cyan('Skipping…'), + ); + process.exit(0); + } + + if (!response.body) + throw new Error( + `Failed to find a "body" in the response form ${downloadUrl}.`, + ); + + await finished( + response.body.pipe(zlib.createGunzip()).pipe(tar.extract(destination)), + ); + + await chmod(destination, 0o755); + + console.log( + chalk.bgGreen('Download complete:'), + chalk.green.underline(join(destination, 'temporal')), + ); +} catch (error) { + reportError(error, 2, removeDirectory); +} diff --git a/scripts/generate-holocene-props.ts b/scripts/generate-holocene-props.ts new file mode 100644 index 000000000..1f9b84516 --- /dev/null +++ b/scripts/generate-holocene-props.ts @@ -0,0 +1,62 @@ +import fs from 'fs'; +import path from 'path'; + +import { svelte2tsx } from 'svelte2tsx'; + +const holocenePath = new URL('../src/lib/holocene', import.meta.url).pathname; + +const getFilesRecursively = (directory: string, root = directory) => { + let files: string[] = []; + const filesInDirectory = fs.readdirSync(directory); + for (const file of filesInDirectory) { + const absolute = path.join(directory, file); + + if (fs.statSync(absolute).isDirectory()) { + files = [...files, ...getFilesRecursively(absolute, root)]; + } else { + files.push(absolute); + } + } + return files + .filter((file) => !file.includes('/icon/')) + .filter((file) => !file.includes('.story.')) + .filter((file) => file.endsWith('.svelte')); +}; + +const holoceneFilePaths = getFilesRecursively(holocenePath); + +const holoceneComponents = holoceneFilePaths + .map((filename) => { + const fileContent = fs.readFileSync(filename, 'utf-8'); + const { exportedNames } = svelte2tsx(fileContent, { + filename, + mode: 'ts', + isTsFile: true, + }); + return { + component: path.basename(filename, '.svelte'), + props: [ + ...( + exportedNames as Map< + string, + { identifierText?: string; required?: boolean; type?: string } + > + ).values(), + ].map((prop) => { + return { + name: prop.identifierText, + required: prop.required, + type: prop.type, + }; + }), + }; + }) + .reduce((acc, curr) => { + acc[curr.component] = curr.props; + return acc; + }, {}); + +fs.writeFileSync( + path.join(holocenePath, 'holocene-components.json'), + JSON.stringify(holoceneComponents, null, 2), +); diff --git a/scripts/get-project-root.ts b/scripts/get-project-root.ts new file mode 100644 index 000000000..da618a9b9 --- /dev/null +++ b/scripts/get-project-root.ts @@ -0,0 +1,16 @@ +import { existsSync } from 'fs'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; + +export const getProjectRoot = (): string => { + let currentDirectory = dirname(fileURLToPath(import.meta.url)); + + while (currentDirectory !== '/') { + if (existsSync(join(currentDirectory, 'package.json'))) { + return currentDirectory; + } + currentDirectory = dirname(currentDirectory); + } + + throw new Error('Project root not found.'); +}; diff --git a/scripts/start-codec-server.ts b/scripts/start-codec-server.ts new file mode 100644 index 000000000..eb934b4c4 --- /dev/null +++ b/scripts/start-codec-server.ts @@ -0,0 +1,24 @@ +import yargs from 'yargs/yargs'; + +import { + type CodecServerOptions, + createCodecServer, +} from '../temporal/codec-server'; + +const args = yargs(process.argv.slice(2)).parse(); + +const options: CodecServerOptions = { + port: args['port'], +}; + +const server = await createCodecServer(options); + +server.start().catch(async (error) => { + console.error(error); + await server.stop(); + process.exit(1); +}); + +process.on('beforeExit', () => { + if (server) server.stop(); +}); diff --git a/scripts/start-temporal-server.ts b/scripts/start-temporal-server.ts new file mode 100644 index 000000000..9411ad763 --- /dev/null +++ b/scripts/start-temporal-server.ts @@ -0,0 +1,26 @@ +import yargs from 'yargs/yargs'; + +import { + createTemporalServer, + type TemporalServer, + type TemporalServerOptions, +} from '../utilities/temporal-server'; + +const args = yargs(process.argv.slice(2)).parse(); + +const options: TemporalServerOptions = { + port: args['port'], + uiPort: args['uiPort'] ?? args['ui-port'], + path: args['path'], + logLevel: args['logLevel'] ?? args['log-level'], + codecEndpoint: args['codecEndpoint'] ?? args['codec-endpoint'], + dbFilename: args['dbFilename'] ?? args['db-filename'], +}; + +const server: TemporalServer = await createTemporalServer(options); + +await server.ready(); + +process.on('beforeExit', async () => { + await server.shutdown(); +}); diff --git a/scripts/start-ui-server.ts b/scripts/start-ui-server.ts new file mode 100644 index 000000000..0cec5f880 --- /dev/null +++ b/scripts/start-ui-server.ts @@ -0,0 +1,9 @@ +import { createUIServer, UIServer } from '../utilities/ui-server'; + +const server: UIServer = await createUIServer(); + +await server.ready(); + +process.on('beforeExit', async () => { + await server.shutdown(); +}); diff --git a/scripts/validate-versions.sh b/scripts/validate-versions.sh new file mode 100755 index 000000000..9ea69f0da --- /dev/null +++ b/scripts/validate-versions.sh @@ -0,0 +1,304 @@ +#!/bin/bash + +# +# Version Validation Script +# +# This script validates that package.json and server/server/version/version.go +# have the same version number. It's used by CI/CD workflows and can be run +# locally by developers. +# + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Default options +QUIET=false +HELP=false + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -q|--quiet) + QUIET=true + shift + ;; + -h|--help) + HELP=true + shift + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +# Show help if requested +if [[ "$HELP" == "true" ]]; then + cat << EOF +Version Validation Script + +Usage: + ./scripts/validate-versions.sh # Run validation + ./scripts/validate-versions.sh --quiet # Run with minimal output + ./scripts/validate-versions.sh --help # Show this help + +Description: + Validates that: + 1. package.json matches server/server/version/version.go UIVersion (Go version is source of truth) + 2. The current version has increased compared to the previous commit (based on Go version) + + This script is used as a gate-keeper for draft release creation. + +Exit codes: + 0 - Versions are in sync AND version has increased + 1 - Validation failed (versions don't match or version didn't increase) +EOF + exit 0 +fi + +# Helper function for colored output +log() { + if [[ "$QUIET" != "true" ]]; then + echo -e "$1" + fi +} + +error() { + echo -e "${RED}$1${NC}" >&2 +} + +success() { + if [[ "$QUIET" != "true" ]]; then + echo -e "${GREEN}$1${NC}" + fi +} + +# Find project root by looking for package.json +find_project_root() { + local current_dir="$(pwd)" + + while [[ "$current_dir" != "/" ]]; do + if [[ -f "$current_dir/package.json" ]]; then + echo "$current_dir" + return 0 + fi + current_dir="$(dirname "$current_dir")" + done + + error "Could not find project root (package.json not found)" + exit 1 +} + +# Get version from package.json +get_package_version() { + local project_root="$1" + local package_json="$project_root/package.json" + + if [[ ! -f "$package_json" ]]; then + error "package.json not found at: $package_json" + exit 1 + fi + + # Use jq if available, otherwise use grep/sed + if command -v jq >/dev/null 2>&1; then + jq -r '.version' "$package_json" + else + # Fallback to grep/sed approach + grep '"version"' "$package_json" | sed 's/.*"version".*:.*"\([^"]*\)".*/\1/' | head -1 + fi +} + +# Get version from version.go +get_go_version() { + local project_root="$1" + local version_go="$project_root/server/server/version/version.go" + + if [[ ! -f "$version_go" ]]; then + error "version.go not found at: $version_go" + exit 1 + fi + + # Extract version from UIVersion = "x.x.x" + grep 'UIVersion.*=' "$version_go" | sed 's/.*"\([^"]*\)".*/\1/' +} + +# Get previous version from last git tag (more reliable than last commit) +get_previous_version() { + local project_root="$1" + + # Change to project root to get relative path for git + local original_dir="$(pwd)" + cd "$project_root" || return 1 + + # Get last tag and extract version + local last_tag + last_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + + # Return to original directory + cd "$original_dir" || return 1 + + # Remove 'v' prefix if present and return + if [[ -n "$last_tag" ]]; then + echo "${last_tag#v}" + else + echo "" + fi +} + +# Compare semantic versions +# Returns: 0 if current > previous, 1 if current < previous, 2 if equal +compare_versions() { + local current="$1" + local previous="$2" + + # If no previous version, treat as newer + if [[ -z "$previous" ]]; then + return 0 # current is newer + fi + + # Parse versions into arrays + IFS='.' read -ra CURRENT_PARTS <<< "$current" + IFS='.' read -ra PREVIOUS_PARTS <<< "$previous" + + # Compare major version + if (( ${CURRENT_PARTS[0]} > ${PREVIOUS_PARTS[0]} )); then + return 0 # current is newer + elif (( ${CURRENT_PARTS[0]} < ${PREVIOUS_PARTS[0]} )); then + return 1 # current is older + fi + + # Compare minor version + if (( ${CURRENT_PARTS[1]} > ${PREVIOUS_PARTS[1]} )); then + return 0 # current is newer + elif (( ${CURRENT_PARTS[1]} < ${PREVIOUS_PARTS[1]} )); then + return 1 # current is older + fi + + # Compare patch version + if (( ${CURRENT_PARTS[2]} > ${PREVIOUS_PARTS[2]} )); then + return 0 # current is newer + elif (( ${CURRENT_PARTS[2]} < ${PREVIOUS_PARTS[2]} )); then + return 1 # current is older + fi + + # Versions are equal + return 2 +} + +# Validate semantic version format (x.y.z) +validate_version_format() { + local version="$1" + + if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + return 1 + fi + + return 0 +} + +# Main validation logic +main() { + log "${BLUE}🔍 Validating version for release readiness...${NC}" + + # Find project root + local project_root + project_root="$(find_project_root)" + log "Project root: $project_root" + log "" + + # Get current version from Go file (source of truth) + local current_version + current_version="$(get_go_version "$project_root")" + if [[ -z "$current_version" ]]; then + error "Failed to extract version from version.go" + exit 1 + fi + + # Get package.json version to check sync + local package_version + package_version="$(get_package_version "$project_root")" + if [[ -z "$package_version" ]]; then + error "Failed to extract version from package.json" + exit 1 + fi + + # Get previous version (from Go file) + local previous_version + previous_version="$(get_previous_version "$project_root")" + + log "${BLUE}🔧 Current version (Go UIVersion): ${NC}$current_version ${YELLOW}[SOURCE OF TRUTH]${NC}" + log "${BLUE}📦 Current package.json version: ${NC}$package_version" + log "${BLUE}📜 Previous version: ${NC}${previous_version:-"(none)"}" + log "" + + # Validate version formats + if ! validate_version_format "$current_version"; then + error "❌ Invalid version format in version.go: $current_version" + error "Expected format: x.y.z (semantic version)" + exit 1 + fi + + if ! validate_version_format "$package_version"; then + error "❌ Invalid version format in package.json: $package_version" + error "Expected format: x.y.z (semantic version)" + exit 1 + fi + + if [[ -n "$previous_version" ]] && ! validate_version_format "$previous_version"; then + error "❌ Invalid previous version format: $previous_version" + error "Expected format: x.y.z (semantic version)" + exit 1 + fi + + # Check 1: package.json must match Go version (source of truth) + if [[ "$package_version" != "$current_version" ]]; then + error "❌ Version sync validation failed!" + error "" + error "package.json version does not match Go UIVersion (source of truth):" + error " Go UIVersion: $current_version ← SOURCE OF TRUTH" + error " package.json: $package_version" + error "" + error "To fix this issue:" + error "1. Use the Version Bump workflow in GitHub Actions, OR" + error "2. Manually update package.json to match version.go" + exit 1 + fi + + success "✅ Version sync check passed: $current_version" + + # Check 2: Version must have increased (based on Go version) + if compare_versions "$current_version" "$previous_version"; then + success "✅ Version increase check passed: ${previous_version:-"(none)"} → $current_version" + log "" + success "🎉 All validations passed! Ready for draft release creation." + exit 0 + else + compare_result=$? + if [[ $compare_result -eq 2 ]]; then + error "❌ Version increase validation failed!" + error "" + error "Version has not changed: $current_version" + error "Draft releases require version increases." + else + error "❌ Version increase validation failed!" + error "" + error "Version decreased: $previous_version → $current_version" + error "Draft releases should only be created when version increases." + fi + error "" + error "To fix this issue:" + error "Use the Version Bump workflow to increment the version:" + error "Actions → Version Bump → Run workflow" + exit 1 + fi +} + +# Run main function +main "$@" \ No newline at end of file diff --git a/scripts/workflows.ts b/scripts/workflows.ts new file mode 100644 index 000000000..418304f9d --- /dev/null +++ b/scripts/workflows.ts @@ -0,0 +1,13 @@ +import { connect, startWorkflows } from '../temporal/client'; +import { runWorkerUntil } from '../temporal/workers'; + +async function main() { + const client = await connect(); + const results = startWorkflows(client); + await runWorkerUntil(results); +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/server/.air.toml b/server/.air.toml new file mode 100644 index 000000000..9efaa876e --- /dev/null +++ b/server/.air.toml @@ -0,0 +1,46 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = ["--env", "development", "start"] + bin = "./ui-server" + cmd = "make build" + delay = 1000 + exclude_dir = ["tmp", "vendor", "testdata", "ui"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html", "yaml"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + post_cmd = [] + pre_cmd = [] + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_root = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + time = false + +[misc] + clean_on_exit = false + +[screen] + clear_on_rebuild = false + keep_scroll = true \ No newline at end of file diff --git a/server/Dockerfile b/server/Dockerfile new file mode 100644 index 000000000..343898bc1 --- /dev/null +++ b/server/Dockerfile @@ -0,0 +1,35 @@ +ARG BASE_SERVER_IMAGE=temporalio/base-server:1.15.11 +ARG BASE_BUILDER_IMAGE=temporalio/base-builder:1.15.4 + +FROM ${BASE_BUILDER_IMAGE} AS server-builder + +WORKDIR /home/server-builder + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . ./ + +RUN make build-server + +##### UI server ##### + +FROM ${BASE_SERVER_IMAGE} AS ui-server + +ARG TEMPORAL_CLOUD_UI="false" + +WORKDIR /home/ui-server + +RUN addgroup -g 1000 temporal +RUN adduser -u 1000 -G temporal -D temporal +RUN mkdir ./config + +COPY --from=server-builder /home/server-builder/ui-server ./ +COPY config/docker.yaml ./config/docker.yaml +COPY docker/start-ui-server.sh ./start-ui-server.sh + +RUN chown temporal:temporal /home/ui-server -R + +EXPOSE 8080 +ENTRYPOINT ["./start-ui-server.sh"] +ENV TEMPORAL_CLOUD_UI=$TEMPORAL_CLOUD_UI diff --git a/server/LICENSE b/server/LICENSE new file mode 100644 index 000000000..4b882ddad --- /dev/null +++ b/server/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/server/Makefile b/server/Makefile new file mode 100644 index 000000000..02ab5134c --- /dev/null +++ b/server/Makefile @@ -0,0 +1,31 @@ +.ONESHELL: +.PHONY: + +all: build + +##### Variables ###### + +ifndef GOPATH +GOPATH := $(shell go env GOPATH) +endif + +GOBIN := $(if $(shell go env GOBIN),$(shell go env GOBIN),$(GOPATH)/bin) +PATH := $(GOBIN):$(PATH) + +COLOR := "\e[1;36m%s\e[0m\n" + +##### Build ##### +build: build-server + +build-server: + go mod tidy + go build -o ui-server ./cmd/server/main.go + +##### Test ##### +test: clean-test-results + @printf $(COLOR) "Running unit tests..." + go test ./... -race + +clean-test-results: + @rm -f test.log + @go clean -testcache diff --git a/server/README.md b/server/README.md new file mode 100644 index 000000000..1f67d0913 --- /dev/null +++ b/server/README.md @@ -0,0 +1,41 @@ +# ui-server + +[![Publish Docker image](https://github.com/temporalio/ui-server/actions/workflows/docker.yml/badge.svg)](https://github.com/temporalio/ui-server/actions/workflows/docker.yml) + +## Development + +https://github.com/temporalio/ui-server is automatically updated to mirror +changes to https://github.com/temporalio/ui/tree/main/server; commits should be +made to the UI repository. + +For contributions follow UI's development guide https://github.com/temporalio/ui + +### Hot Reloading + +The development server uses [Air](https://github.com/air-verse/air) for automatic hot reloading when Go source files change. Air will be automatically installed on first run in development mode, which may take a moment. + +## Configuration + +### CORS Settings + +The server supports flexible CORS configuration through YAML config files: + +```yaml +cors: + allowOrigins: + - "https://example.com" + - "https://app.example.com" + unsafeAllowAllOrigins: false # Default: false + cookieInsecure: false +``` + +**Configuration Options:** + +- `allowOrigins`: List of explicitly allowed origins for CORS requests +- `unsafeAllowAllOrigins`: **⚠️ UNSAFE** - When `true`, allows any origin that makes a request. Only enable for development/testing environments +- `cookieInsecure`: Allow CSRF cookies over insecure connections (useful for VPN scenarios) + +**Security Note:** The `unsafeAllowAllOrigins` setting bypasses CORS security and should never be enabled in production. It dynamically allows the requesting origin, which can expose your API to cross-origin attacks. + +## To View gRPC routes: +[Temporal API Workflowservice](https://github.com/temporalio/api/blob/master/temporal/api/workflowservice/v1/service.proto) diff --git a/server/cmd/server/main.go b/server/cmd/server/main.go new file mode 100644 index 000000000..9cce4eab3 --- /dev/null +++ b/server/cmd/server/main.go @@ -0,0 +1,115 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package main + +import ( + "fmt" + "os" + "path" + + "github.com/temporalio/ui-server/v2/plugins/fs_config_provider" + "github.com/temporalio/ui-server/v2/server" + "github.com/temporalio/ui-server/v2/server/api" + "github.com/temporalio/ui-server/v2/server/headers" + "github.com/temporalio/ui-server/v2/server/server_options" + "github.com/temporalio/ui-server/v2/server/version" + "github.com/urfave/cli/v2" +) + +// main entry point for the web server +func main() { + app := buildCLI() + err := app.Run(os.Args) + if err != nil { + // An unhandled error was returned, wrap it and run it through the default exit code handler. Any errors + // that make it here should be caught further up the call stack and wrapped with cli.Exit and the proper exit code. + cli.HandleExitCoder(cli.Exit(fmt.Sprintf("Unexpected error encountered: %v.", err), 9)) + } +} + +// buildCLI is the main entry point for the web server +func buildCLI() *cli.App { + app := cli.NewApp() + app.Name = "Temporal UI" + app.Usage = "https://github.com/temporalio/ui" + app.Version = version.UIVersion + app.ArgsUsage = " " + app.Flags = []cli.Flag{ + &cli.StringFlag{ + Name: "root", + Aliases: []string{"r"}, + Value: ".", + Usage: "root directory of execution environment", + EnvVars: []string{fs_config_provider.EnvKeyRoot}, + }, + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Value: "config", + Usage: "config dir path relative to root", + EnvVars: []string{fs_config_provider.EnvKeyConfigDir}, + }, + &cli.StringFlag{ + Name: "env", + Aliases: []string{"e"}, + Value: "development", + Usage: "runtime environment", + EnvVars: []string{fs_config_provider.EnvKeyEnvironment}, + }} + + app.Commands = []*cli.Command{ + { + Name: "start", + Usage: "Start Web UI server", + ArgsUsage: " ", + Flags: []cli.Flag{}, + Action: func(c *cli.Context) error { + env := c.String("env") + configDir := path.Join(c.String("root"), c.String("config")) + cfgProvider := fs_config_provider.NewFSConfigProvider(configDir, env) + + cfg, err := cfgProvider.GetConfig() + if err != nil { + return cli.Exit(err, 1) + } + + opts := []server_options.ServerOption{ + server_options.WithConfigProvider(cfgProvider), + server_options.WithAPIMiddleware([]api.Middleware{ + headers.WithForwardHeaders(cfg.ForwardHeaders), + }), + } + + s := server.NewServer(opts...) + defer s.Stop() + err = s.Start() + + if err != nil { + return cli.Exit(fmt.Sprintf("Unable to start server: %v.", err), 1) + } + return cli.Exit("All services are stopped.", 0) + }, + }, + } + return app +} diff --git a/server/cmd/server/main_test.go b/server/cmd/server/main_test.go new file mode 100644 index 000000000..706265451 --- /dev/null +++ b/server/cmd/server/main_test.go @@ -0,0 +1,50 @@ +package main + +import ( + "bytes" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v2" +) + +func TestExitCodesWorkProperly(t *testing.T) { + testcases := []struct { + arguments []string + expectedExitCode int + expectedText string + }{ + {arguments: []string{}, expectedExitCode: 0, expectedText: "USAGE"}, + {arguments: []string{"--config", "/tmp/doesnotexist123", "start"}, expectedExitCode: 1, expectedText: "no config files found"}, + } + + cli.OsExiter = func(int) {} + for idx, tc := range testcases { + t.Run(strconv.Itoa(idx), func(t *testing.T) { + tc := tc + + c := buildCLI() + c.ExitErrHandler = func(_ *cli.Context, err error) {} + + var buf bytes.Buffer + c.Writer = &buf + c.ErrWriter = &buf + + args := append([]string{"./ui-server"}, tc.arguments...) + err := c.Run(args) + + if tc.expectedExitCode != 0 { + assert.Error(t, err) + } + + if err != nil { + ec := err.(cli.ExitCoder) + assert.Equal(t, tc.expectedExitCode, ec.ExitCode()) + assert.Contains(t, ec.Error(), tc.expectedText) + } else { + assert.Contains(t, buf.String(), tc.expectedText) + } + }) + } +} diff --git a/server/config/base.yaml b/server/config/base.yaml new file mode 100644 index 000000000..2db3466c8 --- /dev/null +++ b/server/config/base.yaml @@ -0,0 +1,2 @@ +temporalGrpcAddress: 127.0.0.1:7233 +port: 8233 diff --git a/server/config/development.yaml b/server/config/development.yaml new file mode 100644 index 000000000..5f2fc170d --- /dev/null +++ b/server/config/development.yaml @@ -0,0 +1,60 @@ +publicPath: +port: 8081 +enableUi: true +bannerText: +cors: + cookieInsecure: false + allowOrigins: + - "*" + unsafeAllowAllOrigins: true +refreshInterval: 1m +defaultNamespace: default +showTemporalSystemNamespace: false +feedbackUrl: +notifyOnNewVersion: true +disableWriteActions: false +workflowTerminateDisabled: false +workflowCancelDisabled: false +workflowSignalDisabled: false +workflowUpdateDisabled: false +workflowResetDisabled: false +batchActionsDisabled: false +startWorkflowDisabled: false +hideWorkflowQueryErrors: false +refreshWorkflowCountsDisabled: false +activityCommandsDisabled: false +auth: + enabled: false + providers: + - label: Auth0 oidc # for internal use; in future may expose as button text + type: oidc # for futureproofing; only oidc is supported today + providerUrl: https://myorg.us.auth0.com/ + issuerUrl: "" # needed if the Issuer Url and the Provider Url are different + clientId: xxxxxxxxxxxxxxxxxxxx + clientSecret: xxxxxxxxxxxxxxxxxxxx + scopes: + - openid + - profile + - email + callbackUrl: http://localhost:8080/auth/sso/callback + options: # added as URL query params when redirecting to auth provider + audience: myorg-dev + organization: org_xxxxxxxxxxxx + invitation: +tls: + caFile: + certFile: + keyFile: + caData: + certData: + keyData: + enableHostVerification: false + serverName: +codec: + endpoint: + passAccessToken: false + includeCredentials: false + defaultErrorMessage: + defaultErrorLink: +forwardHeaders: # can be used to pass additional HTTP headers from HTTP requests to Temporal gRPC backend + - X-Forwarded-For diff --git a/server/config/docker.yaml b/server/config/docker.yaml new file mode 100644 index 000000000..26aacb217 --- /dev/null +++ b/server/config/docker.yaml @@ -0,0 +1,79 @@ +# enable-template +# The comment above enables templating support in the server. +# Please see the README for details on how to use this feature. + +temporalGrpcAddress: {{ env "TEMPORAL_ADDRESS" | default "127.0.0.1:7233" }} +port: {{ env "TEMPORAL_UI_PORT" | default "8080" }} +publicPath: {{ env "TEMPORAL_UI_PUBLIC_PATH" | default "" }} +enableUi: {{ env "TEMPORAL_UI_ENABLED" | default "true" }} +bannerText: {{ env "TEMPORAL_BANNER_TEXT" | default "" }} +cloudUi: {{ env "TEMPORAL_CLOUD_UI" | default "false" }} +defaultNamespace: {{ env "TEMPORAL_DEFAULT_NAMESPACE" | default "default" }} +feedbackUrl: {{ env "TEMPORAL_FEEDBACK_URL" | default "" }} +notifyOnNewVersion: {{ env "TEMPORAL_NOTIFY_ON_NEW_VERSION" | default "true" }} +refreshInterval: {{ env "TEMPORAL_CONFIG_REFRESH_INTERVAL" | default "0s" }} +showTemporalSystemNamespace: {{ env "TEMPORAL_SHOW_TEMPORAL_SYSTEM_NAMESPACE" | default "false" }} +disableWriteActions: {{ env "TEMPORAL_DISABLE_WRITE_ACTIONS" | default "false" }} +workflowTerminateDisabled: {{ env "TEMPORAL_WORKFLOW_TERMINATE_DISABLED" | default "false" }} +workflowCancelDisabled: {{ env "TEMPORAL_WORKFLOW_CANCEL_DISABLED" | default "false" }} +workflowSignalDisabled: {{ env "TEMPORAL_WORKFLOW_SIGNAL_DISABLED" | default "false" }} +workflowUpdateDisabled: {{ env "TEMPORAL_WORKFLOW_UPDATE_DISABLED" | default "false" }} +workflowResetDisabled: {{ env "TEMPORAL_WORKFLOW_RESET_DISABLED" | default "false" }} +batchActionsDisabled: {{ env "TEMPORAL_BATCH_ACTIONS_DISABLED" | default "false" }} +startWorkflowDisabled: {{ env "TEMPORAL_START_WORKFLOW_DISABLED" | default "false" }} +hideWorkflowQueryErrors: {{ env "TEMPORAL_HIDE_WORKFLOW_QUERY_ERRORS" | default "false" }} +refreshWorkflowCountsDisabled: {{ env "TEMPORAL_REFRESH_WORKFLOW_COUNTS_DISABLED" | default "false" }} +activityCommandsDisabled: {{ env "TEMPORAL_ACTIVITY_COMMANDS_DISABLED" | default "false" }} +cors: + cookieInsecure: {{ env "TEMPORAL_CSRF_COOKIE_INSECURE" | default "false" }} + unsafeAllowAllOrigins: {{ env "TEMPORAL_CORS_UNSAFE_ALLOW_ALL_ORIGINS" | default "false" }} + allowOrigins: + # override framework's default that allows all origins "*" + {{- if env "TEMPORAL_CORS_ORIGINS" }} + {{- range env "TEMPORAL_CORS_ORIGINS" | split "," }} + - {{ . }} + {{- end }} + {{- else }} + - "http://localhost:8080" + {{- end }} +tls: + caFile: {{ env "TEMPORAL_TLS_CA" | default "" }} + certFile: {{ env "TEMPORAL_TLS_CERT" | default "" }} + keyFile: {{ env "TEMPORAL_TLS_KEY" | default "" }} + caData: {{ env "TEMPORAL_TLS_CA_DATA" | default "" }} + certData: {{ env "TEMPORAL_TLS_CERT_DATA" | default "" }} + keyData: {{ env "TEMPORAL_TLS_KEY_DATA" | default "" }} + enableHostVerification: {{ env "TEMPORAL_TLS_ENABLE_HOST_VERIFICATION" | default "false" }} + serverName: {{ env "TEMPORAL_TLS_SERVER_NAME" | default "" }} +auth: + enabled: {{ env "TEMPORAL_AUTH_ENABLED" | default "false" }} + providers: + - label: {{ env "TEMPORAL_AUTH_LABEL" | default "sso" }} + type: {{ env "TEMPORAL_AUTH_TYPE" | default "oidc" }} + providerUrl: {{ env "TEMPORAL_AUTH_PROVIDER_URL" }} + issuerUrl: {{ env "TEMPORAL_AUTH_ISSUER_URL" | default "" }} + clientId: {{ env "TEMPORAL_AUTH_CLIENT_ID" }} + clientSecret: {{ env "TEMPORAL_AUTH_CLIENT_SECRET" }} + callbackUrl: {{ env "TEMPORAL_AUTH_CALLBACK_URL" }} + useIdTokenAsBearer: {{ env "TEMPORAL_AUTH_USE_ID_TOKEN_AS_BEARER" | default "false" }} + scopes: + {{- if env "TEMPORAL_AUTH_SCOPES" }} + {{- range env "TEMPORAL_AUTH_SCOPES" | split "," }} + - {{ . }} + {{- end }} + {{- end }} +codec: + endpoint: {{ env "TEMPORAL_CODEC_ENDPOINT" | default "" }} + passAccessToken: {{ env "TEMPORAL_CODEC_PASS_ACCESS_TOKEN" | default "false" }} + includeCredentials: {{ env "TEMPORAL_CODEC_INCLUDE_CREDENTIALS" | default "false" }} + defaultErrorMessage: {{ env "TEMPORAL_CODEC_DEFAULT_ERROR_MESSAGE" | default "" }} + defaultErrorLink: {{ env "TEMPORAL_CODEC_DEFAULT_ERROR_LINK" | default "" }} + +forwardHeaders: +{{- if env "TEMPORAL_FORWARD_HEADERS" }} +{{- range env "TEMPORAL_FORWARD_HEADERS" | split "," }} + - {{ . }} +{{- end }} +{{- end }} + +hideLogs: {{ env "TEMPORAL_HIDE_LOGS" | default "false" }} diff --git a/server/config/e2e.yaml b/server/config/e2e.yaml new file mode 100644 index 000000000..dcbf7803c --- /dev/null +++ b/server/config/e2e.yaml @@ -0,0 +1,4 @@ +temporalGrpcAddress: 127.0.0.1:7233 +port: 8080 +codec: + endpoint: http://localhost:8888 diff --git a/server/docker/README.md b/server/docker/README.md new file mode 100644 index 000000000..d987796ec --- /dev/null +++ b/server/docker/README.md @@ -0,0 +1,49 @@ +# Pre-built images + +ui-server can be consumed from Docker Hub: https://hub.docker.com/r/temporalio/ui + +Check out our [docker-compose](https://github.com/temporalio/docker-compose) for a quick set up with Temporal Server. + +## Quickstart for production + +An example command to run the UI with Auth and TLS enabled + +**Note**: For proper security you will also want to [enable authorization](https://docs.temporal.io/security/#authorization) on Temporal Server. + +```shellscript +docker run \ + -e TEMPORAL_ADDRESS=127.0.0.1:7233 \ + -e TEMPORAL_UI_PORT=8080 \ + -e TEMPORAL_AUTH_ENABLED=true \ + -e TEMPORAL_AUTH_PROVIDER_URL=https://accounts.google.com \ + -e TEMPORAL_AUTH_CLIENT_ID=xxxxx-xxxx.apps.googleusercontent.com \ + -e TEMPORAL_AUTH_CLIENT_SECRET=xxxxxxxxxxxxxxx \ + -e TEMPORAL_AUTH_CALLBACK_URL=https://xxxx.com:8080/auth/sso/callback \ + -e TEMPORAL_AUTH_SCOPES=openid,email,profile \ + -e TEMPORAL_TLS_CA=../ca.cert \ + -e TEMPORAL_TLS_CERT=../cluster.pem \ + -e TEMPORAL_TLS_KEY=../cluster.key \ + -e TEMPORAL_TLS_ENABLE_HOST_VERIFICATION=true \ + -e TEMPORAL_TLS_SERVER_NAME=tls-server \ + temporalio/ui:latest +``` + +For all env options see [Config template file](./config/docker.yaml) or [Configuration Docs](https://docs.temporal.io/references/web-ui-configuration) + +## Config template + +The config file can use templating which uses [go's text templating](https://pkg.go.dev/text/template) and [Sprig's helpers](https://masterminds.github.io/sprig/). + +To enable templating you can add a commented "# enable-template" line at the top of the file. + +Note: Previously our docker images supported Dockerize for templating. This is now deprecated, and will be removed soon. If you have an existing custom config templating using dockerize, it will need to be adjusted to use Sprig's helpers. A good example of this is the `default` helper which uses a different argument ordering to dockerize. + +## Serve UI under a sub-path + +To change the public path under which the UI is served you can use the TEMPORAL_UI_PUBLIC_PATH environment variable. + +``` +docker run -d --network host -e TEMPORAL_UI_PUBLIC_PATH=/custom-path -t temporal-ui +``` + +Then navigate to http://localhost:8080/custom-path \ No newline at end of file diff --git a/server/docker/start-ui-server.sh b/server/docker/start-ui-server.sh new file mode 100755 index 000000000..8cb3a2b1c --- /dev/null +++ b/server/docker/start-ui-server.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -eu -o pipefail + +if [ -f ./config/config-template.yaml ]; then + >&2 echo "Custom config templates should now be mounted at: /home/ui-server/config/docker.yaml" + >&2 echo "Please see the README for details on config templating support" + dockerize -template ./config-template.yaml:./config/docker.yaml +fi + +# Run bash instead of ui-server if "bash" is passed as an argument (convenient to debug docker image). +for arg in "$@" ; do [[ ${arg} == "bash" ]] && bash && exit 0 ; done + +exec ./ui-server --env docker start diff --git a/server/go.mod b/server/go.mod new file mode 100644 index 000000000..421aab2bb --- /dev/null +++ b/server/go.mod @@ -0,0 +1,50 @@ +module github.com/temporalio/ui-server/v2 + +go 1.23.0 + +require ( + github.com/Masterminds/sprig/v3 v3.3.0 + github.com/coreos/go-oidc/v3 v3.11.0 + github.com/gomarkdown/markdown v0.0.0-20241105142532-d03b89096d81 + github.com/gorilla/securecookie v1.1.1 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 + github.com/labstack/echo/v4 v4.13.4 + github.com/stretchr/testify v1.10.0 + github.com/urfave/cli/v2 v2.3.0 + go.temporal.io/api v1.50.0 + golang.org/x/net v0.40.0 + golang.org/x/oauth2 v0.30.0 + google.golang.org/grpc v1.66.0 + google.golang.org/protobuf v1.36.5 + gopkg.in/validator.v2 v2.0.0-20210331031555-b37d688a7fb0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-jose/go-jose/v4 v4.0.5 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/huandu/xstrings v1.5.0 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + golang.org/x/crypto v0.38.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.11.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed // indirect +) diff --git a/server/go.sum b/server/go.sum new file mode 100644 index 000000000..1dd59e006 --- /dev/null +++ b/server/go.sum @@ -0,0 +1,101 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/coreos/go-oidc/v3 v3.11.0 h1:Ia3MxdwpSw702YW0xgfmP1GVCMA9aEFWu12XUZ3/OtI= +github.com/coreos/go-oidc/v3 v3.11.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= +github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= +github.com/gomarkdown/markdown v0.0.0-20241105142532-d03b89096d81 h1:5lyLWsV+qCkoYqsKUDuycESh9DEIPVKN6iCFeL7ag50= +github.com/gomarkdown/markdown v0.0.0-20241105142532-d03b89096d81/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA= +github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +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/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +go.temporal.io/api v1.50.0 h1:7s8Cn+fKfNx9G0v2Ge9We6X2WiCA3JvJ9JryeNbx1Bc= +go.temporal.io/api v1.50.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +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/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed h1:3RgNmBoI9MZhsj3QxC+AP/qQhNwpCLOvYDYYsFrhFt0= +google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed h1:J6izYgfBXAI3xTKLgxzTmUltdYaLsuBxFCgDHWJ/eXg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= +google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/validator.v2 v2.0.0-20210331031555-b37d688a7fb0 h1:EFLtLCwd8tGN+r/ePz3cvRtdsfYNhDEdt/vp6qsT+0A= +gopkg.in/validator.v2 v2.0.0-20210331031555-b37d688a7fb0/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/server/plugins/fs_config_provider/fs_config_provider.go b/server/plugins/fs_config_provider/fs_config_provider.go new file mode 100644 index 000000000..fd5bf9a2b --- /dev/null +++ b/server/plugins/fs_config_provider/fs_config_provider.go @@ -0,0 +1,55 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package fs_config_provider + +import ( + "github.com/temporalio/ui-server/v2/server/config" +) + +type ( + fsConfigProvider struct { + configDir string + env string + } +) + +var _ config.ConfigProvider = (*fsConfigProvider)(nil) + +// NewFSConfigProvider creates a default file system based Config Provider +func NewFSConfigProvider(configDir string, env string) config.ConfigProvider { + return &fsConfigProvider{ + configDir, + env, + } +} + +func (a *fsConfigProvider) GetConfig() (*config.Config, error) { + cfg, err := LoadConfig(a.configDir, a.env) + if err != nil { + return nil, err + } + + return cfg, nil +} diff --git a/server/plugins/fs_config_provider/loader.go b/server/plugins/fs_config_provider/loader.go new file mode 100644 index 000000000..25b7e04c7 --- /dev/null +++ b/server/plugins/fs_config_provider/loader.go @@ -0,0 +1,187 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package fs_config_provider + +import ( + "bufio" + "bytes" + "fmt" + "html/template" + "io" + "io/ioutil" + "log" + "os" + "strings" + + "gopkg.in/validator.v2" + "gopkg.in/yaml.v3" + + "github.com/Masterminds/sprig/v3" + "github.com/temporalio/ui-server/v2/server/config" +) + +const ( + // EnvKeyRoot the environment variable key for runtime root dir + EnvKeyRoot = "TEMPORAL_ROOT" + // EnvKeyConfigDir the environment variable key for config dir + EnvKeyConfigDir = "TEMPORAL_CONFIG_DIR" + // EnvKeyEnvironment is the environment variable key for environment + EnvKeyEnvironment = "TEMPORAL_ENVIRONMENT" +) + +const ( + baseFile = "base.yaml" + envDevelopment = "development" + defaultConfigDir = "config" + enableTemplate = "enable-template" + commentSearchLimit = 1024 +) + +// Load loads the configuration from a set of +// yaml config files found in the config directory +// +// The loader first fetches the set of files matching +// a pre-determined naming convention, then sorts +// them by hierarchy order and after that, simply +// loads the files one after another with the +// key/values in the later files overriding the key/values +// in the earlier files +// +// The hierarchy is as follows from lowest to highest +// +// base.yaml +// env.yaml -- environment is one of the input params ex-development +func Load(configDir string, config interface{}, env string) error { + if len(env) == 0 { + env = envDevelopment + } + if len(configDir) == 0 { + configDir = defaultConfigDir + } + + log.Printf("Loading config; env=%v,configDir=%v\n", env, configDir) + + files, err := getConfigFiles(configDir, env) + if err != nil { + return err + } + + log.Printf("Loading config files=%v\n", files) + + templateFuncs := sprig.FuncMap() + + for _, f := range files { + // This is tagged nosec because the file names being read are for config files that are not user supplied + // #nosec + data, err := ioutil.ReadFile(f) + if err != nil { + return err + } + + // If the config file contains "enable-template" in a comment within the first 1KB, then + // we will treat the file as a template and render it. + templating, err := checkTemplatingEnabled(data) + if err != nil { + return err + } + + if templating { + tpl, err := template.New("config").Funcs(templateFuncs).Parse(string(data)) + if err != nil { + return fmt.Errorf("template parsing error: %w", err) + } + + var rendered bytes.Buffer + err = tpl.Execute(&rendered, nil) + if err != nil { + return fmt.Errorf("template execution error: %w", err) + } + data = rendered.Bytes() + } + + err = yaml.Unmarshal(data, config) + if err != nil { + return err + } + } + + return validator.Validate(config) +} + +// Helper function for loading configuration +func LoadConfig(configDir string, env string) (*config.Config, error) { + config := config.Config{} + err := Load(configDir, &config, env) + if err != nil { + return nil, fmt.Errorf("config file corrupted: %w", err) + } + return &config, nil +} + +func checkTemplatingEnabled(content []byte) (bool, error) { + scanner := bufio.NewScanner(io.LimitReader(bytes.NewReader(content), commentSearchLimit)) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + + if strings.HasPrefix(line, "#") && strings.Contains(line, enableTemplate) { + return true, nil + } + } + + return false, scanner.Err() +} + +// getConfigFiles returns the list of config files to +// process in the hierarchy order +func getConfigFiles(configDir string, env string) ([]string, error) { + + candidates := []string{ + path(configDir, baseFile), + path(configDir, file(env, "yaml")), + } + + var result []string + + for _, c := range candidates { + if _, err := os.Stat(c); err != nil { + continue + } + result = append(result, c) + } + + if len(result) == 0 { + return nil, fmt.Errorf("no config files found within %v", configDir) + } + + return result, nil +} + +func file(name string, suffix string) string { + return name + "." + suffix +} + +func path(dir string, file string) string { + return dir + "/" + file +} diff --git a/server/plugins/fs_config_provider/loader_test.go b/server/plugins/fs_config_provider/loader_test.go new file mode 100644 index 000000000..42e825c8a --- /dev/null +++ b/server/plugins/fs_config_provider/loader_test.go @@ -0,0 +1,130 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package fs_config_provider + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +const fileMode = os.FileMode(0644) + +type ( + LoaderSuite struct { + *require.Assertions + suite.Suite + } + + itemsConfig struct { + Item1 string `yaml:"item1"` + Item2 string `yaml:"item2"` + } + + testConfig struct { + Items itemsConfig `yaml:"items"` + } +) + +func TestLoaderSuite(t *testing.T) { + suite.Run(t, new(LoaderSuite)) +} + +func (s *LoaderSuite) SetupTest() { + s.Assertions = require.New(s.T()) +} + +func (s *LoaderSuite) TestBaseYaml() { + + dir, err := ioutil.TempDir("", "loader.testBaseYaml") + s.Nil(err) + defer os.RemoveAll(dir) + + data := buildConfig("") + err = ioutil.WriteFile(path(dir, "base.yaml"), []byte(data), fileMode) + s.Nil(err) + + envs := []string{"", "prod"} + + for _, env := range envs { + var cfg testConfig + err = Load(dir, &cfg, env) + s.Nil(err) + s.Equal("hello_", cfg.Items.Item1) + s.Equal("world_", cfg.Items.Item2) + } +} + +func (s *LoaderSuite) TestHierarchy() { + dir, err := ioutil.TempDir("", "loader.testHierarchy") + s.Nil(err) + defer os.RemoveAll(dir) + + s.createFile(dir, "base.yaml", "") + s.createFile(dir, "development.yaml", "development") + s.createFile(dir, "prod.yaml", "prod") + s.createFile(dir, "prod_dca.yaml", "prod") + + testCases := []struct { + env string + item1 string + item2 string + }{ + {"", "hello_development", "world_development"}, + {"development", "hello_development", "world_development"}, + {"prod", "hello_prod", "world_prod"}, + } + + for _, tc := range testCases { + var cfg testConfig + err = Load(dir, &cfg, tc.env) + s.Nil(err) + s.Equal(tc.item1, cfg.Items.Item1) + s.Equal(tc.item2, cfg.Items.Item2) + } +} + +func (s *LoaderSuite) TestInvalidPath() { + var cfg testConfig + err := Load("", &cfg, "prod") + s.NotNil(err) +} + +func (s *LoaderSuite) createFile(dir string, file string, env string) { + err := ioutil.WriteFile(path(dir, file), []byte(buildConfig(env)), fileMode) + s.Nil(err) +} + +func buildConfig(env string) string { + item1 := "hello_" + env + item2 := "world_" + env + return ` + items: + item1: ` + item1 + ` + item2: ` + item2 +} diff --git a/server/proto/dependencies/github.com/gogo/googleapis/google/api/annotations.proto b/server/proto/dependencies/github.com/gogo/googleapis/google/api/annotations.proto new file mode 100644 index 000000000..8ff420984 --- /dev/null +++ b/server/proto/dependencies/github.com/gogo/googleapis/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} \ No newline at end of file diff --git a/server/proto/dependencies/github.com/gogo/googleapis/google/api/http.proto b/server/proto/dependencies/github.com/gogo/googleapis/google/api/http.proto new file mode 100644 index 000000000..7d0b228cc --- /dev/null +++ b/server/proto/dependencies/github.com/gogo/googleapis/google/api/http.proto @@ -0,0 +1,375 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} \ No newline at end of file diff --git a/server/proto/dependencies/github.com/gogo/googleapis/google/api/httpbody.proto b/server/proto/dependencies/github.com/gogo/googleapis/google/api/httpbody.proto new file mode 100644 index 000000000..8439be5c3 --- /dev/null +++ b/server/proto/dependencies/github.com/gogo/googleapis/google/api/httpbody.proto @@ -0,0 +1,81 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/protobuf/any.proto"; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/httpbody;httpbody"; +option java_multiple_files = true; +option java_outer_classname = "HttpBodyProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Message that represents an arbitrary HTTP body. It should only be used for +// payload formats that can't be represented as JSON, such as raw binary or +// an HTML page. +// +// +// This message can be used both in streaming and non-streaming API methods in +// the request as well as the response. +// +// It can be used as a top-level request field, which is convenient if one +// wants to extract parameters from either the URL or HTTP template into the +// request fields and also want access to the raw HTTP body. +// +// Example: +// +// message GetResourceRequest { +// // A unique request id. +// string request_id = 1; +// +// // The raw HTTP body is bound to this field. +// google.api.HttpBody http_body = 2; +// +// } +// +// service ResourceService { +// rpc GetResource(GetResourceRequest) +// returns (google.api.HttpBody); +// rpc UpdateResource(google.api.HttpBody) +// returns (google.protobuf.Empty); +// +// } +// +// Example with streaming methods: +// +// service CaldavService { +// rpc GetCalendar(stream google.api.HttpBody) +// returns (stream google.api.HttpBody); +// rpc UpdateCalendar(stream google.api.HttpBody) +// returns (stream google.api.HttpBody); +// +// } +// +// Use of this type only changes how the request and response bodies are +// handled, all other features will continue to work unchanged. +message HttpBody { + // The HTTP Content-Type header value specifying the content type of the body. + string content_type = 1; + + // The HTTP request/response body as raw binary. + bytes data = 2; + + // Application specific response metadata. Must be set in the first response + // for streaming APIs. + repeated google.protobuf.Any extensions = 3; +} \ No newline at end of file diff --git a/server/proto/dependencies/github.com/gogo/googleapis/google/rpc/code.proto b/server/proto/dependencies/github.com/gogo/googleapis/google/rpc/code.proto new file mode 100644 index 000000000..d115da149 --- /dev/null +++ b/server/proto/dependencies/github.com/gogo/googleapis/google/rpc/code.proto @@ -0,0 +1,186 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.rpc; + +option go_package = "google.golang.org/genproto/googleapis/rpc/code;code"; +option java_multiple_files = true; +option java_outer_classname = "CodeProto"; +option java_package = "com.google.rpc"; +option objc_class_prefix = "RPC"; + +// The canonical error codes for gRPC APIs. +// +// +// Sometimes multiple error codes may apply. Services should return +// the most specific error code that applies. For example, prefer +// `OUT_OF_RANGE` over `FAILED_PRECONDITION` if both codes apply. +// Similarly prefer `NOT_FOUND` or `ALREADY_EXISTS` over `FAILED_PRECONDITION`. +enum Code { + // Not an error; returned on success + // + // HTTP Mapping: 200 OK + OK = 0; + + // The operation was cancelled, typically by the caller. + // + // HTTP Mapping: 499 Client Closed Request + CANCELLED = 1; + + // Unknown error. For example, this error may be returned when + // a `Status` value received from another address space belongs to + // an error space that is not known in this address space. Also + // errors raised by APIs that do not return enough error information + // may be converted to this error. + // + // HTTP Mapping: 500 Internal Server Error + UNKNOWN = 2; + + // The client specified an invalid argument. Note that this differs + // from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` indicates arguments + // that are problematic regardless of the state of the system + // (e.g., a malformed file name). + // + // HTTP Mapping: 400 Bad Request + INVALID_ARGUMENT = 3; + + // The deadline expired before the operation could complete. For operations + // that change the state of the system, this error may be returned + // even if the operation has completed successfully. For example, a + // successful response from a server could have been delayed long + // enough for the deadline to expire. + // + // HTTP Mapping: 504 Gateway Timeout + DEADLINE_EXCEEDED = 4; + + // Some requested entity (e.g., file or directory) was not found. + // + // Note to server developers: if a request is denied for an entire class + // of users, such as gradual feature rollout or undocumented whitelist, + // `NOT_FOUND` may be used. If a request is denied for some users within + // a class of users, such as user-based access control, `PERMISSION_DENIED` + // must be used. + // + // HTTP Mapping: 404 Not Found + NOT_FOUND = 5; + + // The entity that a client attempted to create (e.g., file or directory) + // already exists. + // + // HTTP Mapping: 409 Conflict + ALREADY_EXISTS = 6; + + // The caller does not have permission to execute the specified + // operation. `PERMISSION_DENIED` must not be used for rejections + // caused by exhausting some resource (use `RESOURCE_EXHAUSTED` + // instead for those errors). `PERMISSION_DENIED` must not be + // used if the caller can not be identified (use `UNAUTHENTICATED` + // instead for those errors). This error code does not imply the + // request is valid or the requested entity exists or satisfies + // other pre-conditions. + // + // HTTP Mapping: 403 Forbidden + PERMISSION_DENIED = 7; + + // The request does not have valid authentication credentials for the + // operation. + // + // HTTP Mapping: 401 Unauthorized + UNAUTHENTICATED = 16; + + // Some resource has been exhausted, perhaps a per-user quota, or + // perhaps the entire file system is out of space. + // + // HTTP Mapping: 429 Too Many Requests + RESOURCE_EXHAUSTED = 8; + + // The operation was rejected because the system is not in a state + // required for the operation's execution. For example, the directory + // to be deleted is non-empty, an rmdir operation is applied to + // a non-directory, etc. + // + // Service implementors can use the following guidelines to decide + // between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`: + // (a) Use `UNAVAILABLE` if the client can retry just the failing call. + // (b) Use `ABORTED` if the client should retry at a higher level + // (e.g., when a client-specified test-and-set fails, indicating the + // client should restart a read-modify-write sequence). + // (c) Use `FAILED_PRECONDITION` if the client should not retry until + // the system state has been explicitly fixed. E.g., if an "rmdir" + // fails because the directory is non-empty, `FAILED_PRECONDITION` + // should be returned since the client should not retry unless + // the files are deleted from the directory. + // + // HTTP Mapping: 400 Bad Request + FAILED_PRECONDITION = 9; + + // The operation was aborted, typically due to a concurrency issue such as + // a sequencer check failure or transaction abort. + // + // See the guidelines above for deciding between `FAILED_PRECONDITION`, + // `ABORTED`, and `UNAVAILABLE`. + // + // HTTP Mapping: 409 Conflict + ABORTED = 10; + + // The operation was attempted past the valid range. E.g., seeking or + // reading past end-of-file. + // + // Unlike `INVALID_ARGUMENT`, this error indicates a problem that may + // be fixed if the system state changes. For example, a 32-bit file + // system will generate `INVALID_ARGUMENT` if asked to read at an + // offset that is not in the range [0,2^32-1], but it will generate + // `OUT_OF_RANGE` if asked to read from an offset past the current + // file size. + // + // There is a fair bit of overlap between `FAILED_PRECONDITION` and + // `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific + // error) when it applies so that callers who are iterating through + // a space can easily look for an `OUT_OF_RANGE` error to detect when + // they are done. + // + // HTTP Mapping: 400 Bad Request + OUT_OF_RANGE = 11; + + // The operation is not implemented or is not supported/enabled in this + // service. + // + // HTTP Mapping: 501 Not Implemented + UNIMPLEMENTED = 12; + + // Internal errors. This means that some invariants expected by the + // underlying system have been broken. This error code is reserved + // for serious errors. + // + // HTTP Mapping: 500 Internal Server Error + INTERNAL = 13; + + // The service is currently unavailable. This is most likely a + // transient condition, which can be corrected by retrying with + // a backoff. Note that it is not always safe to retry + // non-idempotent operations. + // + // See the guidelines above for deciding between `FAILED_PRECONDITION`, + // `ABORTED`, and `UNAVAILABLE`. + // + // HTTP Mapping: 503 Service Unavailable + UNAVAILABLE = 14; + + // Unrecoverable data loss or corruption. + // + // HTTP Mapping: 500 Internal Server Error + DATA_LOSS = 15; +} \ No newline at end of file diff --git a/server/proto/dependencies/github.com/gogo/googleapis/google/rpc/error_details.proto b/server/proto/dependencies/github.com/gogo/googleapis/google/rpc/error_details.proto new file mode 100644 index 000000000..981d18ce3 --- /dev/null +++ b/server/proto/dependencies/github.com/gogo/googleapis/google/rpc/error_details.proto @@ -0,0 +1,249 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.rpc; + +import "google/protobuf/duration.proto"; + +option go_package = "google.golang.org/genproto/googleapis/rpc/errdetails;errdetails"; +option java_multiple_files = true; +option java_outer_classname = "ErrorDetailsProto"; +option java_package = "com.google.rpc"; +option objc_class_prefix = "RPC"; + +// Describes when the clients can retry a failed request. Clients could ignore +// the recommendation here or retry when this information is missing from error +// responses. +// +// It's always recommended that clients should use exponential backoff when +// retrying. +// +// Clients should wait until `retry_delay` amount of time has passed since +// receiving the error response before retrying. If retrying requests also +// fail, clients should use an exponential backoff scheme to gradually increase +// the delay between retries based on `retry_delay`, until either a maximum +// number of retries have been reached or a maximum retry delay cap has been +// reached. +message RetryInfo { + // Clients should wait at least this long between retrying the same request. + google.protobuf.Duration retry_delay = 1; +} + +// Describes additional debugging info. +message DebugInfo { + // The stack trace entries indicating where the error occurred. + repeated string stack_entries = 1; + + // Additional debugging information provided by the server. + string detail = 2; +} + +// Describes how a quota check failed. +// +// For example if a daily limit was exceeded for the calling project, +// a service could respond with a QuotaFailure detail containing the project +// id and the description of the quota limit that was exceeded. If the +// calling project hasn't enabled the service in the developer console, then +// a service could respond with the project id and set `service_disabled` +// to true. +// +// Also see RetryInfo and Help types for other details about handling a +// quota failure. +message QuotaFailure { + // A message type used to describe a single quota violation. For example, a + // daily quota or a custom quota that was exceeded. + message Violation { + // The subject on which the quota check failed. + // For example, "clientip:" or "project:". + string subject = 1; + + // A description of how the quota check failed. Clients can use this + // description to find more about the quota configuration in the service's + // public documentation, or find the relevant quota limit to adjust through + // developer console. + // + // For example: "Service disabled" or "Daily Limit for read operations + // exceeded". + string description = 2; + } + + // Describes all quota violations. + repeated Violation violations = 1; +} + +// Describes the cause of the error with structured details. +// +// Example of an error when contacting the "pubsub.googleapis.com" API when it +// is not enabled: +// +// { "reason": "API_DISABLED" +// "domain": "googleapis.com" +// "metadata": { +// "resource": "projects/123", +// "service": "pubsub.googleapis.com" +// } +// } +// +// This response indicates that the pubsub.googleapis.com API is not enabled. +// +// Example of an error that is returned when attempting to create a Spanner +// instance in a region that is out of stock: +// +// { "reason": "STOCKOUT" +// "domain": "spanner.googleapis.com", +// "metadata": { +// "availableRegions": "us-central1,us-east2" +// } +// } +message ErrorInfo { + // The reason of the error. This is a constant value that identifies the + // proximate cause of the error. Error reasons are unique within a particular + // domain of errors. This should be at most 63 characters and match + // /[A-Z0-9_]+/. + string reason = 1; + + // The logical grouping to which the "reason" belongs. The error domain + // is typically the registered service name of the tool or product that + // generates the error. Example: "pubsub.googleapis.com". If the error is + // generated by some common infrastructure, the error domain must be a + // globally unique value that identifies the infrastructure. For Google API + // infrastructure, the error domain is "googleapis.com". + string domain = 2; + + // Additional structured details about this error. + // + // Keys should match /[a-zA-Z0-9-_]/ and be limited to 64 characters in + // length. When identifying the current value of an exceeded limit, the units + // should be contained in the key, not the value. For example, rather than + // {"instanceLimit": "100/request"}, should be returned as, + // {"instanceLimitPerRequest": "100"}, if the client exceeds the number of + // instances that can be created in a single (batch) request. + map metadata = 3; +} + +// Describes what preconditions have failed. +// +// For example, if an RPC failed because it required the Terms of Service to be +// acknowledged, it could list the terms of service violation in the +// PreconditionFailure message. +message PreconditionFailure { + // A message type used to describe a single precondition failure. + message Violation { + // The type of PreconditionFailure. We recommend using a service-specific + // enum type to define the supported precondition violation subjects. For + // example, "TOS" for "Terms of Service violation". + string type = 1; + + // The subject, relative to the type, that failed. + // For example, "google.com/cloud" relative to the "TOS" type would indicate + // which terms of service is being referenced. + string subject = 2; + + // A description of how the precondition failed. Developers can use this + // description to understand how to fix the failure. + // + // For example: "Terms of service not accepted". + string description = 3; + } + + // Describes all precondition violations. + repeated Violation violations = 1; +} + +// Describes violations in a client request. This error type focuses on the +// syntactic aspects of the request. +message BadRequest { + // A message type used to describe a single bad request field. + message FieldViolation { + // A path leading to a field in the request body. The value will be a + // sequence of dot-separated identifiers that identify a protocol buffer + // field. E.g., "field_violations.field" would identify this field. + string field = 1; + + // A description of why the request element is bad. + string description = 2; + } + + // Describes all violations in a client request. + repeated FieldViolation field_violations = 1; +} + +// Contains metadata about the request that clients can attach when filing a bug +// or providing other forms of feedback. +message RequestInfo { + // An opaque string that should only be interpreted by the service generating + // it. For example, it can be used to identify requests in the service's logs. + string request_id = 1; + + // Any data that was used to serve this request. For example, an encrypted + // stack trace that can be sent back to the service provider for debugging. + string serving_data = 2; +} + +// Describes the resource that is being accessed. +message ResourceInfo { + // A name for the type of resource being accessed, e.g. "sql table", + // "cloud storage bucket", "file", "Google calendar"; or the type URL + // of the resource: e.g. "type.googleapis.com/google.pubsub.v1.Topic". + string resource_type = 1; + + // The name of the resource being accessed. For example, a shared calendar + // name: "example.com_4fghdhgsrgh@group.calendar.google.com", if the current + // error is [google.rpc.Code.PERMISSION_DENIED][google.rpc.Code.PERMISSION_DENIED]. + string resource_name = 2; + + // The owner of the resource (optional). + // For example, "user:" or "project:". + string owner = 3; + + // Describes what error is encountered when accessing this resource. + // For example, updating a cloud project may require the `writer` permission + // on the developer console project. + string description = 4; +} + +// Provides links to documentation or for performing an out of band action. +// +// For example, if a quota check failed with an error indicating the calling +// project hasn't enabled the accessed service, this can contain a URL pointing +// directly to the right place in the developer console to flip the bit. +message Help { + // Describes a URL link. + message Link { + // Describes what the link offers. + string description = 1; + + // The URL of the link. + string url = 2; + } + + // URL(s) pointing to additional information on handling the current error. + repeated Link links = 1; +} + +// Provides a localized error message that is safe to return to the user +// which can be attached to an RPC error. +message LocalizedMessage { + // The locale used following the specification defined at + // http://www.rfc-editor.org/rfc/bcp/bcp47.txt. + // Examples are: "en-US", "fr-CH", "es-MX" + string locale = 1; + + // The localized error message in the above locale. + string message = 2; +} \ No newline at end of file diff --git a/server/proto/dependencies/github.com/gogo/googleapis/google/rpc/status.proto b/server/proto/dependencies/github.com/gogo/googleapis/google/rpc/status.proto new file mode 100644 index 000000000..5bd51aa2f --- /dev/null +++ b/server/proto/dependencies/github.com/gogo/googleapis/google/rpc/status.proto @@ -0,0 +1,47 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.rpc; + +import "google/protobuf/any.proto"; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/rpc/status;status"; +option java_multiple_files = true; +option java_outer_classname = "StatusProto"; +option java_package = "com.google.rpc"; +option objc_class_prefix = "RPC"; + +// The `Status` type defines a logical error model that is suitable for +// different programming environments, including REST APIs and RPC APIs. It is +// used by [gRPC](https://github.com/grpc). Each `Status` message contains +// three pieces of data: error code, error message, and error details. +// +// You can find out more about this error model and how to work with it in the +// [API Design Guide](https://cloud.google.com/apis/design/errors). +message Status { + // The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + int32 code = 1; + + // A developer-facing error message, which should be in English. Any + // user-facing error message should be localized and sent in the + // [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + string message = 2; + + // A list of messages that carry the error details. There is a common set of + // message types for APIs to use. + repeated google.protobuf.Any details = 3; +} \ No newline at end of file diff --git a/server/proto/dependencies/github.com/gogo/protobuf/gogoproto/gogo.pb.golden b/server/proto/dependencies/github.com/gogo/protobuf/gogoproto/gogo.pb.golden new file mode 100644 index 000000000..f6502e4b9 --- /dev/null +++ b/server/proto/dependencies/github.com/gogo/protobuf/gogoproto/gogo.pb.golden @@ -0,0 +1,45 @@ +// Code generated by protoc-gen-go. +// source: gogo.proto +// DO NOT EDIT! + +package gogoproto + +import proto "github.com/gogo/protobuf/proto" +import json "encoding/json" +import math "math" +import google_protobuf "github.com/gogo/protobuf/protoc-gen-gogo/descriptor" + +// Reference proto, json, and math imports to suppress error if they are not otherwise used. +var _ = proto.Marshal +var _ = &json.SyntaxError{} +var _ = math.Inf + +var E_Nullable = &proto.ExtensionDesc{ + ExtendedType: (*google_protobuf.FieldOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 51235, + Name: "gogoproto.nullable", + Tag: "varint,51235,opt,name=nullable", +} + +var E_Embed = &proto.ExtensionDesc{ + ExtendedType: (*google_protobuf.FieldOptions)(nil), + ExtensionType: (*bool)(nil), + Field: 51236, + Name: "gogoproto.embed", + Tag: "varint,51236,opt,name=embed", +} + +var E_Customtype = &proto.ExtensionDesc{ + ExtendedType: (*google_protobuf.FieldOptions)(nil), + ExtensionType: (*string)(nil), + Field: 51237, + Name: "gogoproto.customtype", + Tag: "bytes,51237,opt,name=customtype", +} + +func init() { + proto.RegisterExtension(E_Nullable) + proto.RegisterExtension(E_Embed) + proto.RegisterExtension(E_Customtype) +} diff --git a/server/proto/dependencies/github.com/gogo/protobuf/gogoproto/gogo.proto b/server/proto/dependencies/github.com/gogo/protobuf/gogoproto/gogo.proto new file mode 100644 index 000000000..b80c85653 --- /dev/null +++ b/server/proto/dependencies/github.com/gogo/protobuf/gogoproto/gogo.proto @@ -0,0 +1,144 @@ +// Protocol Buffers for Go with Gadgets +// +// Copyright (c) 2013, The GoGo Authors. All rights reserved. +// http://github.com/gogo/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto2"; +package gogoproto; + +import "google/protobuf/descriptor.proto"; + +option java_package = "com.google.protobuf"; +option java_outer_classname = "GoGoProtos"; +option go_package = "github.com/gogo/protobuf/gogoproto"; + +extend google.protobuf.EnumOptions { + optional bool goproto_enum_prefix = 62001; + optional bool goproto_enum_stringer = 62021; + optional bool enum_stringer = 62022; + optional string enum_customname = 62023; + optional bool enumdecl = 62024; +} + +extend google.protobuf.EnumValueOptions { + optional string enumvalue_customname = 66001; +} + +extend google.protobuf.FileOptions { + optional bool goproto_getters_all = 63001; + optional bool goproto_enum_prefix_all = 63002; + optional bool goproto_stringer_all = 63003; + optional bool verbose_equal_all = 63004; + optional bool face_all = 63005; + optional bool gostring_all = 63006; + optional bool populate_all = 63007; + optional bool stringer_all = 63008; + optional bool onlyone_all = 63009; + + optional bool equal_all = 63013; + optional bool description_all = 63014; + optional bool testgen_all = 63015; + optional bool benchgen_all = 63016; + optional bool marshaler_all = 63017; + optional bool unmarshaler_all = 63018; + optional bool stable_marshaler_all = 63019; + + optional bool sizer_all = 63020; + + optional bool goproto_enum_stringer_all = 63021; + optional bool enum_stringer_all = 63022; + + optional bool unsafe_marshaler_all = 63023; + optional bool unsafe_unmarshaler_all = 63024; + + optional bool goproto_extensions_map_all = 63025; + optional bool goproto_unrecognized_all = 63026; + optional bool gogoproto_import = 63027; + optional bool protosizer_all = 63028; + optional bool compare_all = 63029; + optional bool typedecl_all = 63030; + optional bool enumdecl_all = 63031; + + optional bool goproto_registration = 63032; + optional bool messagename_all = 63033; + + optional bool goproto_sizecache_all = 63034; + optional bool goproto_unkeyed_all = 63035; +} + +extend google.protobuf.MessageOptions { + optional bool goproto_getters = 64001; + optional bool goproto_stringer = 64003; + optional bool verbose_equal = 64004; + optional bool face = 64005; + optional bool gostring = 64006; + optional bool populate = 64007; + optional bool stringer = 67008; + optional bool onlyone = 64009; + + optional bool equal = 64013; + optional bool description = 64014; + optional bool testgen = 64015; + optional bool benchgen = 64016; + optional bool marshaler = 64017; + optional bool unmarshaler = 64018; + optional bool stable_marshaler = 64019; + + optional bool sizer = 64020; + + optional bool unsafe_marshaler = 64023; + optional bool unsafe_unmarshaler = 64024; + + optional bool goproto_extensions_map = 64025; + optional bool goproto_unrecognized = 64026; + + optional bool protosizer = 64028; + optional bool compare = 64029; + + optional bool typedecl = 64030; + + optional bool messagename = 64033; + + optional bool goproto_sizecache = 64034; + optional bool goproto_unkeyed = 64035; +} + +extend google.protobuf.FieldOptions { + optional bool nullable = 65001; + optional bool embed = 65002; + optional string customtype = 65003; + optional string customname = 65004; + optional string jsontag = 65005; + optional string moretags = 65006; + optional string casttype = 65007; + optional string castkey = 65008; + optional string castvalue = 65009; + + optional bool stdtime = 65010; + optional bool stdduration = 65011; + optional bool wktpointer = 65012; + +} diff --git a/server/proto/dependencies/github.com/grpc-ecosystem/grpc-gateway/internal/descriptor/apiconfig/apiconfig.proto b/server/proto/dependencies/github.com/grpc-ecosystem/grpc-gateway/internal/descriptor/apiconfig/apiconfig.proto new file mode 100644 index 000000000..aeecd7773 --- /dev/null +++ b/server/proto/dependencies/github.com/grpc-ecosystem/grpc-gateway/internal/descriptor/apiconfig/apiconfig.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package grpc.gateway.internal.descriptor.apiconfig; + +option go_package = "github.com/grpc-ecosystem/grpc-gateway/v2/internal/descriptor/apiconfig"; + +import "google/api/http.proto"; + +// GrpcAPIService represents a stripped down version of google.api.Service . +// Compare to https://github.com/googleapis/googleapis/blob/master/google/api/service.proto +// The original imports 23 other protobuf files we are not interested in. If a significant +// subset (>50%) of these start being reproduced in this file we should swap to using the +// full generated version instead. +// +// For the purposes of the gateway generator we only consider a small subset of all +// available features google supports in their service descriptions. Thanks to backwards +// compatibility guarantees by protobuf it is safe for us to remove the other fields. +message GrpcAPIService { + // Http Rule. + google.api.Http http = 1; +} diff --git a/server/proto/dependencies/github.com/grpc-ecosystem/grpc-gateway/internal/descriptor/openapiconfig/openapiconfig.proto b/server/proto/dependencies/github.com/grpc-ecosystem/grpc-gateway/internal/descriptor/openapiconfig/openapiconfig.proto new file mode 100644 index 000000000..f0aa69ee2 --- /dev/null +++ b/server/proto/dependencies/github.com/grpc-ecosystem/grpc-gateway/internal/descriptor/openapiconfig/openapiconfig.proto @@ -0,0 +1,51 @@ +syntax = "proto3"; + +package grpc.gateway.internal.descriptor.openapiconfig; + +option go_package = "github.com/grpc-ecosystem/grpc-gateway/v2/internal/descriptor/openapiconfig"; + +import "protoc-gen-openapiv2/options/openapiv2.proto"; + +// OpenAPIFileOption represents OpenAPI options on a file +message OpenAPIFileOption { + string file = 1; + grpc.gateway.protoc_gen_openapiv2.options.Swagger option = 2; +} + +// OpenAPIMethodOption represents OpenAPI options on a method +message OpenAPIMethodOption { + string method = 1; + grpc.gateway.protoc_gen_openapiv2.options.Operation option = 2; +} + +// OpenAPIMessageOption represents OpenAPI options on a message +message OpenAPIMessageOption { + string message = 1; + grpc.gateway.protoc_gen_openapiv2.options.Schema option = 2; +} + +// OpenAPIServiceOption represents OpenAPI options on a service +message OpenAPIServiceOption { + string service = 1; // ex: Service + grpc.gateway.protoc_gen_openapiv2.options.Tag option = 2; +} + +// OpenAPIFieldOption represents OpenAPI options on a field +message OpenAPIFieldOption { + string field = 1; + grpc.gateway.protoc_gen_openapiv2.options.JSONSchema option = 2; +} + +// OpenAPIOptions represents OpenAPI protobuf options +message OpenAPIOptions { + repeated OpenAPIFileOption file = 1; + repeated OpenAPIMethodOption method = 2; + repeated OpenAPIMessageOption message = 3; + repeated OpenAPIServiceOption service = 4; + repeated OpenAPIFieldOption field = 5; +} + +// OpenAPIConfig represents a set of OpenAPI options +message OpenAPIConfig { + OpenAPIOptions openapi_options = 1; +} diff --git a/server/proto/dependencies/github.com/grpc-ecosystem/grpc-gateway/protoc-gen-openapiv2/options/annotations.proto b/server/proto/dependencies/github.com/grpc-ecosystem/grpc-gateway/protoc-gen-openapiv2/options/annotations.proto new file mode 100644 index 000000000..1c189e206 --- /dev/null +++ b/server/proto/dependencies/github.com/grpc-ecosystem/grpc-gateway/protoc-gen-openapiv2/options/annotations.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; + +package grpc.gateway.protoc_gen_openapiv2.options; + +option go_package = "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"; + +import "google/protobuf/descriptor.proto"; +import "protoc-gen-openapiv2/options/openapiv2.proto"; + +extend google.protobuf.FileOptions { + // ID assigned by protobuf-global-extension-registry@google.com for gRPC-Gateway project. + // + // All IDs are the same, as assigned. It is okay that they are the same, as they extend + // different descriptor messages. + Swagger openapiv2_swagger = 1042; +} +extend google.protobuf.MethodOptions { + // ID assigned by protobuf-global-extension-registry@google.com for gRPC-Gateway project. + // + // All IDs are the same, as assigned. It is okay that they are the same, as they extend + // different descriptor messages. + Operation openapiv2_operation = 1042; +} +extend google.protobuf.MessageOptions { + // ID assigned by protobuf-global-extension-registry@google.com for gRPC-Gateway project. + // + // All IDs are the same, as assigned. It is okay that they are the same, as they extend + // different descriptor messages. + Schema openapiv2_schema = 1042; +} +extend google.protobuf.ServiceOptions { + // ID assigned by protobuf-global-extension-registry@google.com for gRPC-Gateway project. + // + // All IDs are the same, as assigned. It is okay that they are the same, as they extend + // different descriptor messages. + Tag openapiv2_tag = 1042; +} +extend google.protobuf.FieldOptions { + // ID assigned by protobuf-global-extension-registry@google.com for gRPC-Gateway project. + // + // All IDs are the same, as assigned. It is okay that they are the same, as they extend + // different descriptor messages. + JSONSchema openapiv2_field = 1042; +} diff --git a/server/proto/dependencies/github.com/grpc-ecosystem/grpc-gateway/protoc-gen-openapiv2/options/openapiv2.proto b/server/proto/dependencies/github.com/grpc-ecosystem/grpc-gateway/protoc-gen-openapiv2/options/openapiv2.proto new file mode 100644 index 000000000..7be1fb572 --- /dev/null +++ b/server/proto/dependencies/github.com/grpc-ecosystem/grpc-gateway/protoc-gen-openapiv2/options/openapiv2.proto @@ -0,0 +1,645 @@ +syntax = "proto3"; + +package grpc.gateway.protoc_gen_openapiv2.options; + +option go_package = "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"; + +import "google/protobuf/struct.proto"; + +// Scheme describes the schemes supported by the OpenAPI Swagger +// and Operation objects. +enum Scheme { + UNKNOWN = 0; + HTTP = 1; + HTTPS = 2; + WS = 3; + WSS = 4; +} + +// `Swagger` is a representation of OpenAPI v2 specification's Swagger object. +// +// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#swaggerObject +// +// Example: +// +// option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { +// info: { +// title: "Echo API"; +// version: "1.0"; +// description: "; +// contact: { +// name: "gRPC-Gateway project"; +// url: "https://github.com/grpc-ecosystem/grpc-gateway"; +// email: "none@example.com"; +// }; +// license: { +// name: "BSD 3-Clause License"; +// url: "https://github.com/grpc-ecosystem/grpc-gateway/blob/master/LICENSE.txt"; +// }; +// }; +// schemes: HTTPS; +// consumes: "application/json"; +// produces: "application/json"; +// }; +// +message Swagger { + // Specifies the OpenAPI Specification version being used. It can be + // used by the OpenAPI UI and other clients to interpret the API listing. The + // value MUST be "2.0". + string swagger = 1; + // Provides metadata about the API. The metadata can be used by the + // clients if needed. + Info info = 2; + // The host (name or ip) serving the API. This MUST be the host only and does + // not include the scheme nor sub-paths. It MAY include a port. If the host is + // not included, the host serving the documentation is to be used (including + // the port). The host does not support path templating. + string host = 3; + // The base path on which the API is served, which is relative to the host. If + // it is not included, the API is served directly under the host. The value + // MUST start with a leading slash (/). The basePath does not support path + // templating. + // Note that using `base_path` does not change the endpoint paths that are + // generated in the resulting OpenAPI file. If you wish to use `base_path` + // with relatively generated OpenAPI paths, the `base_path` prefix must be + // manually removed from your `google.api.http` paths and your code changed to + // serve the API from the `base_path`. + string base_path = 4; + // The transfer protocol of the API. Values MUST be from the list: "http", + // "https", "ws", "wss". If the schemes is not included, the default scheme to + // be used is the one used to access the OpenAPI definition itself. + repeated Scheme schemes = 5; + // A list of MIME types the APIs can consume. This is global to all APIs but + // can be overridden on specific API calls. Value MUST be as described under + // Mime Types. + repeated string consumes = 6; + // A list of MIME types the APIs can produce. This is global to all APIs but + // can be overridden on specific API calls. Value MUST be as described under + // Mime Types. + repeated string produces = 7; + // field 8 is reserved for 'paths'. + reserved 8; + // field 9 is reserved for 'definitions', which at this time are already + // exposed as and customizable as proto messages. + reserved 9; + // An object to hold responses that can be used across operations. This + // property does not define global responses for all operations. + map responses = 10; + // Security scheme definitions that can be used across the specification. + SecurityDefinitions security_definitions = 11; + // A declaration of which security schemes are applied for the API as a whole. + // The list of values describes alternative security schemes that can be used + // (that is, there is a logical OR between the security requirements). + // Individual operations can override this definition. + repeated SecurityRequirement security = 12; + // field 13 is reserved for 'tags', which are supposed to be exposed as and + // customizable as proto services. TODO(ivucica): add processing of proto + // service objects into OpenAPI v2 Tag objects. + reserved 13; + // Additional external documentation. + ExternalDocumentation external_docs = 14; + map extensions = 15; +} + +// `Operation` is a representation of OpenAPI v2 specification's Operation object. +// +// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#operationObject +// +// Example: +// +// service EchoService { +// rpc Echo(SimpleMessage) returns (SimpleMessage) { +// option (google.api.http) = { +// get: "/v1/example/echo/{id}" +// }; +// +// option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { +// summary: "Get a message."; +// operation_id: "getMessage"; +// tags: "echo"; +// responses: { +// key: "200" +// value: { +// description: "OK"; +// } +// } +// }; +// } +// } +message Operation { + // A list of tags for API documentation control. Tags can be used for logical + // grouping of operations by resources or any other qualifier. + repeated string tags = 1; + // A short summary of what the operation does. For maximum readability in the + // swagger-ui, this field SHOULD be less than 120 characters. + string summary = 2; + // A verbose explanation of the operation behavior. GFM syntax can be used for + // rich text representation. + string description = 3; + // Additional external documentation for this operation. + ExternalDocumentation external_docs = 4; + // Unique string used to identify the operation. The id MUST be unique among + // all operations described in the API. Tools and libraries MAY use the + // operationId to uniquely identify an operation, therefore, it is recommended + // to follow common programming naming conventions. + string operation_id = 5; + // A list of MIME types the operation can consume. This overrides the consumes + // definition at the OpenAPI Object. An empty value MAY be used to clear the + // global definition. Value MUST be as described under Mime Types. + repeated string consumes = 6; + // A list of MIME types the operation can produce. This overrides the produces + // definition at the OpenAPI Object. An empty value MAY be used to clear the + // global definition. Value MUST be as described under Mime Types. + repeated string produces = 7; + // field 8 is reserved for 'parameters'. + reserved 8; + // The list of possible responses as they are returned from executing this + // operation. + map responses = 9; + // The transfer protocol for the operation. Values MUST be from the list: + // "http", "https", "ws", "wss". The value overrides the OpenAPI Object + // schemes definition. + repeated Scheme schemes = 10; + // Declares this operation to be deprecated. Usage of the declared operation + // should be refrained. Default value is false. + bool deprecated = 11; + // A declaration of which security schemes are applied for this operation. The + // list of values describes alternative security schemes that can be used + // (that is, there is a logical OR between the security requirements). This + // definition overrides any declared top-level security. To remove a top-level + // security declaration, an empty array can be used. + repeated SecurityRequirement security = 12; + map extensions = 13; +} + +// `Header` is a representation of OpenAPI v2 specification's Header object. +// +// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#headerObject +// +message Header { + // `Description` is a short description of the header. + string description = 1; + // The type of the object. The value MUST be one of "string", "number", "integer", or "boolean". The "array" type is not supported. + string type = 2; + // `Format` The extending format for the previously mentioned type. + string format = 3; + // field 4 is reserved for 'items', but in OpenAPI-specific way. + reserved 4; + // field 5 is reserved `Collection Format` Determines the format of the array if type array is used. + reserved 5; + // `Default` Declares the value of the header that the server will use if none is provided. + // See: https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-6.2. + // Unlike JSON Schema this value MUST conform to the defined type for the header. + string default = 6; + // field 7 is reserved for 'maximum'. + reserved 7; + // field 8 is reserved for 'exclusiveMaximum'. + reserved 8; + // field 9 is reserved for 'minimum'. + reserved 9; + // field 10 is reserved for 'exclusiveMinimum'. + reserved 10; + // field 11 is reserved for 'maxLength'. + reserved 11; + // field 12 is reserved for 'minLength'. + reserved 12; + // 'Pattern' See https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.2.3. + string pattern = 13; + // field 14 is reserved for 'maxItems'. + reserved 14; + // field 15 is reserved for 'minItems'. + reserved 15; + // field 16 is reserved for 'uniqueItems'. + reserved 16; + // field 17 is reserved for 'enum'. + reserved 17; + // field 18 is reserved for 'multipleOf'. + reserved 18; +} + +// `Response` is a representation of OpenAPI v2 specification's Response object. +// +// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#responseObject +// +message Response { + // `Description` is a short description of the response. + // GFM syntax can be used for rich text representation. + string description = 1; + // `Schema` optionally defines the structure of the response. + // If `Schema` is not provided, it means there is no content to the response. + Schema schema = 2; + // `Headers` A list of headers that are sent with the response. + // `Header` name is expected to be a string in the canonical format of the MIME header key + // See: https://golang.org/pkg/net/textproto/#CanonicalMIMEHeaderKey + map headers = 3; + // `Examples` gives per-mimetype response examples. + // See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#example-object + map examples = 4; + map extensions = 5; +} + +// `Info` is a representation of OpenAPI v2 specification's Info object. +// +// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#infoObject +// +// Example: +// +// option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { +// info: { +// title: "Echo API"; +// version: "1.0"; +// description: "; +// contact: { +// name: "gRPC-Gateway project"; +// url: "https://github.com/grpc-ecosystem/grpc-gateway"; +// email: "none@example.com"; +// }; +// license: { +// name: "BSD 3-Clause License"; +// url: "https://github.com/grpc-ecosystem/grpc-gateway/blob/master/LICENSE.txt"; +// }; +// }; +// ... +// }; +// +message Info { + // The title of the application. + string title = 1; + // A short description of the application. GFM syntax can be used for rich + // text representation. + string description = 2; + // The Terms of Service for the API. + string terms_of_service = 3; + // The contact information for the exposed API. + Contact contact = 4; + // The license information for the exposed API. + License license = 5; + // Provides the version of the application API (not to be confused + // with the specification version). + string version = 6; + map extensions = 7; +} + +// `Contact` is a representation of OpenAPI v2 specification's Contact object. +// +// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#contactObject +// +// Example: +// +// option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { +// info: { +// ... +// contact: { +// name: "gRPC-Gateway project"; +// url: "https://github.com/grpc-ecosystem/grpc-gateway"; +// email: "none@example.com"; +// }; +// ... +// }; +// ... +// }; +// +message Contact { + // The identifying name of the contact person/organization. + string name = 1; + // The URL pointing to the contact information. MUST be in the format of a + // URL. + string url = 2; + // The email address of the contact person/organization. MUST be in the format + // of an email address. + string email = 3; +} + +// `License` is a representation of OpenAPI v2 specification's License object. +// +// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#licenseObject +// +// Example: +// +// option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { +// info: { +// ... +// license: { +// name: "BSD 3-Clause License"; +// url: "https://github.com/grpc-ecosystem/grpc-gateway/blob/master/LICENSE.txt"; +// }; +// ... +// }; +// ... +// }; +// +message License { + // The license name used for the API. + string name = 1; + // A URL to the license used for the API. MUST be in the format of a URL. + string url = 2; +} + +// `ExternalDocumentation` is a representation of OpenAPI v2 specification's +// ExternalDocumentation object. +// +// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#externalDocumentationObject +// +// Example: +// +// option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = { +// ... +// external_docs: { +// description: "More about gRPC-Gateway"; +// url: "https://github.com/grpc-ecosystem/grpc-gateway"; +// } +// ... +// }; +// +message ExternalDocumentation { + // A short description of the target documentation. GFM syntax can be used for + // rich text representation. + string description = 1; + // The URL for the target documentation. Value MUST be in the format + // of a URL. + string url = 2; +} + +// `Schema` is a representation of OpenAPI v2 specification's Schema object. +// +// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#schemaObject +// +message Schema { + JSONSchema json_schema = 1; + // Adds support for polymorphism. The discriminator is the schema property + // name that is used to differentiate between other schema that inherit this + // schema. The property name used MUST be defined at this schema and it MUST + // be in the required property list. When used, the value MUST be the name of + // this schema or any schema that inherits it. + string discriminator = 2; + // Relevant only for Schema "properties" definitions. Declares the property as + // "read only". This means that it MAY be sent as part of a response but MUST + // NOT be sent as part of the request. Properties marked as readOnly being + // true SHOULD NOT be in the required list of the defined schema. Default + // value is false. + bool read_only = 3; + // field 4 is reserved for 'xml'. + reserved 4; + // Additional external documentation for this schema. + ExternalDocumentation external_docs = 5; + // A free-form property to include an example of an instance for this schema in JSON. + // This is copied verbatim to the output. + string example = 6; +} + +// `JSONSchema` represents properties from JSON Schema taken, and as used, in +// the OpenAPI v2 spec. +// +// This includes changes made by OpenAPI v2. +// +// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#schemaObject +// +// See also: https://cswr.github.io/JsonSchema/spec/basic_types/, +// https://github.com/json-schema-org/json-schema-spec/blob/master/schema.json +// +// Example: +// +// message SimpleMessage { +// option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { +// json_schema: { +// title: "SimpleMessage" +// description: "A simple message." +// required: ["id"] +// } +// }; +// +// // Id represents the message identifier. +// string id = 1; [ +// (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { +// {description: "The unique identifier of the simple message." +// }]; +// } +// +message JSONSchema { + // field 1 is reserved for '$id', omitted from OpenAPI v2. + reserved 1; + // field 2 is reserved for '$schema', omitted from OpenAPI v2. + reserved 2; + // Ref is used to define an external reference to include in the message. + // This could be a fully qualified proto message reference, and that type must + // be imported into the protofile. If no message is identified, the Ref will + // be used verbatim in the output. + // For example: + // `ref: ".google.protobuf.Timestamp"`. + string ref = 3; + // field 4 is reserved for '$comment', omitted from OpenAPI v2. + reserved 4; + // The title of the schema. + string title = 5; + // A short description of the schema. + string description = 6; + string default = 7; + bool read_only = 8; + // A free-form property to include a JSON example of this field. This is copied + // verbatim to the output swagger.json. Quotes must be escaped. + // This property is the same for 2.0 and 3.0.0 https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/3.0.0.md#schemaObject https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#schemaObject + string example = 9; + double multiple_of = 10; + // Maximum represents an inclusive upper limit for a numeric instance. The + // value of MUST be a number, + double maximum = 11; + bool exclusive_maximum = 12; + // minimum represents an inclusive lower limit for a numeric instance. The + // value of MUST be a number, + double minimum = 13; + bool exclusive_minimum = 14; + uint64 max_length = 15; + uint64 min_length = 16; + string pattern = 17; + // field 18 is reserved for 'additionalItems', omitted from OpenAPI v2. + reserved 18; + // field 19 is reserved for 'items', but in OpenAPI-specific way. + // TODO(ivucica): add 'items'? + reserved 19; + uint64 max_items = 20; + uint64 min_items = 21; + bool unique_items = 22; + // field 23 is reserved for 'contains', omitted from OpenAPI v2. + reserved 23; + uint64 max_properties = 24; + uint64 min_properties = 25; + repeated string required = 26; + // field 27 is reserved for 'additionalProperties', but in OpenAPI-specific + // way. TODO(ivucica): add 'additionalProperties'? + reserved 27; + // field 28 is reserved for 'definitions', omitted from OpenAPI v2. + reserved 28; + // field 29 is reserved for 'properties', but in OpenAPI-specific way. + // TODO(ivucica): add 'additionalProperties'? + reserved 29; + // following fields are reserved, as the properties have been omitted from + // OpenAPI v2: + // patternProperties, dependencies, propertyNames, const + reserved 30 to 33; + // Items in 'array' must be unique. + repeated string array = 34; + + enum JSONSchemaSimpleTypes { + UNKNOWN = 0; + ARRAY = 1; + BOOLEAN = 2; + INTEGER = 3; + NULL = 4; + NUMBER = 5; + OBJECT = 6; + STRING = 7; + } + + repeated JSONSchemaSimpleTypes type = 35; + // `Format` + string format = 36; + // following fields are reserved, as the properties have been omitted from + // OpenAPI v2: contentMediaType, contentEncoding, if, then, else + reserved 37 to 41; + // field 42 is reserved for 'allOf', but in OpenAPI-specific way. + // TODO(ivucica): add 'allOf'? + reserved 42; + // following fields are reserved, as the properties have been omitted from + // OpenAPI v2: + // anyOf, oneOf, not + reserved 43 to 45; + // Items in `enum` must be unique https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.1 + repeated string enum = 46; +} + +// `Tag` is a representation of OpenAPI v2 specification's Tag object. +// +// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#tagObject +// +message Tag { + // field 1 is reserved for 'name'. In our generator, this is (to be) extracted + // from the name of proto service, and thus not exposed to the user, as + // changing tag object's name would break the link to the references to the + // tag in individual operation specifications. + // + // TODO(ivucica): Add 'name' property. Use it to allow override of the name of + // global Tag object, then use that name to reference the tag throughout the + // OpenAPI file. + reserved 1; + // A short description for the tag. GFM syntax can be used for rich text + // representation. + string description = 2; + // Additional external documentation for this tag. + ExternalDocumentation external_docs = 3; +} + +// `SecurityDefinitions` is a representation of OpenAPI v2 specification's +// Security Definitions object. +// +// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#securityDefinitionsObject +// +// A declaration of the security schemes available to be used in the +// specification. This does not enforce the security schemes on the operations +// and only serves to provide the relevant details for each scheme. +message SecurityDefinitions { + // A single security scheme definition, mapping a "name" to the scheme it + // defines. + map security = 1; +} + +// `SecurityScheme` is a representation of OpenAPI v2 specification's +// Security Scheme object. +// +// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#securitySchemeObject +// +// Allows the definition of a security scheme that can be used by the +// operations. Supported schemes are basic authentication, an API key (either as +// a header or as a query parameter) and OAuth2's common flows (implicit, +// password, application and access code). +message SecurityScheme { + // The type of the security scheme. Valid values are "basic", + // "apiKey" or "oauth2". + enum Type { + TYPE_INVALID = 0; + TYPE_BASIC = 1; + TYPE_API_KEY = 2; + TYPE_OAUTH2 = 3; + } + + // The location of the API key. Valid values are "query" or "header". + enum In { + IN_INVALID = 0; + IN_QUERY = 1; + IN_HEADER = 2; + } + + // The flow used by the OAuth2 security scheme. Valid values are + // "implicit", "password", "application" or "accessCode". + enum Flow { + FLOW_INVALID = 0; + FLOW_IMPLICIT = 1; + FLOW_PASSWORD = 2; + FLOW_APPLICATION = 3; + FLOW_ACCESS_CODE = 4; + } + + // The type of the security scheme. Valid values are "basic", + // "apiKey" or "oauth2". + Type type = 1; + // A short description for security scheme. + string description = 2; + // The name of the header or query parameter to be used. + // Valid for apiKey. + string name = 3; + // The location of the API key. Valid values are "query" or + // "header". + // Valid for apiKey. + In in = 4; + // The flow used by the OAuth2 security scheme. Valid values are + // "implicit", "password", "application" or "accessCode". + // Valid for oauth2. + Flow flow = 5; + // The authorization URL to be used for this flow. This SHOULD be in + // the form of a URL. + // Valid for oauth2/implicit and oauth2/accessCode. + string authorization_url = 6; + // The token URL to be used for this flow. This SHOULD be in the + // form of a URL. + // Valid for oauth2/password, oauth2/application and oauth2/accessCode. + string token_url = 7; + // The available scopes for the OAuth2 security scheme. + // Valid for oauth2. + Scopes scopes = 8; + map extensions = 9; +} + +// `SecurityRequirement` is a representation of OpenAPI v2 specification's +// Security Requirement object. +// +// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#securityRequirementObject +// +// Lists the required security schemes to execute this operation. The object can +// have multiple security schemes declared in it which are all required (that +// is, there is a logical AND between the schemes). +// +// The name used for each property MUST correspond to a security scheme +// declared in the Security Definitions. +message SecurityRequirement { + // If the security scheme is of type "oauth2", then the value is a list of + // scope names required for the execution. For other security scheme types, + // the array MUST be empty. + message SecurityRequirementValue { + repeated string scope = 1; + } + // Each name must correspond to a security scheme which is declared in + // the Security Definitions. If the security scheme is of type "oauth2", + // then the value is a list of scope names required for the execution. + // For other security scheme types, the array MUST be empty. + map security_requirement = 1; +} + +// `Scopes` is a representation of OpenAPI v2 specification's Scopes object. +// +// See: https://github.com/OAI/OpenAPI-Specification/blob/3.0.0/versions/2.0.md#scopesObject +// +// Lists the available scopes for an OAuth2 security scheme. +message Scopes { + // Maps between a name of a scope to a short description of it (as the value + // of the property). + map scope = 1; +} diff --git a/server/server/api/handler.go b/server/server/api/handler.go new file mode 100644 index 000000000..1dc7ac039 --- /dev/null +++ b/server/server/api/handler.go @@ -0,0 +1,228 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package api + +import ( + "fmt" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/labstack/echo/v4" + "golang.org/x/net/context" + "google.golang.org/grpc" + + "github.com/temporalio/ui-server/v2/server/auth" + "github.com/temporalio/ui-server/v2/server/config" + "github.com/temporalio/ui-server/v2/server/rpc" + "github.com/temporalio/ui-server/v2/server/version" + "go.temporal.io/api/operatorservice/v1" + "go.temporal.io/api/serviceerror" + "go.temporal.io/api/temporalproto" + "go.temporal.io/api/workflowservice/v1" + + // DO NOT REMOVE + _ "go.temporal.io/api/temporalproto" +) + +type Auth struct { + Enabled bool + Options []string +} + +type CodecResponse struct { + Endpoint string + PassAccessToken bool + IncludeCredentials bool + DefaultErrorMessage string + DefaultErrorLink string +} + +type SettingsResponse struct { + Auth *Auth + BannerText string + DefaultNamespace string + ShowTemporalSystemNamespace bool + FeedbackURL string + NotifyOnNewVersion bool + Codec *CodecResponse + Version string + DisableWriteActions bool + WorkflowTerminateDisabled bool + WorkflowCancelDisabled bool + WorkflowSignalDisabled bool + WorkflowUpdateDisabled bool + WorkflowResetDisabled bool + BatchActionsDisabled bool + StartWorkflowDisabled bool + HideWorkflowQueryErrors bool + RefreshWorkflowCountsDisabled bool + ActivityCommandsDisabled bool +} + +func TemporalAPIHandler(cfgProvider *config.ConfigProviderWithRefresh, apiMiddleware []Middleware, conn *grpc.ClientConn) echo.HandlerFunc { + return func(c echo.Context) error { + err := auth.ValidateAuthHeaderExists(c, cfgProvider) + if err != nil { + return err + } + + mux, err := getTemporalClientMux(c, conn, apiMiddleware) + if err != nil { + return err + } + + mux.ServeHTTP(c.Response(), c.Request()) + return nil + } +} + +func CreateGRPCConnection(cfgProvider *config.ConfigProviderWithRefresh) (*grpc.ClientConn, error) { + cfg, err := cfgProvider.GetConfig() + if err != nil { + return nil, err + } + + tls, err := rpc.CreateTLSConfig(cfg.TemporalGRPCAddress, &cfg.TLS) + if err != nil { + return nil, fmt.Errorf("Unable to read TLS configs: %w", err) + } + + conn := rpc.CreateGRPCConnection(cfg.TemporalGRPCAddress, tls) + return conn, nil +} + +func GetSettings(cfgProvider *config.ConfigProviderWithRefresh) func(echo.Context) error { + return func(c echo.Context) error { + cfg, err := cfgProvider.GetConfig() + if err != nil { + return c.JSON(http.StatusInternalServerError, err) + } + + var options []string + if len(cfg.Auth.Providers) != 0 { + authProviderCfg := cfg.Auth.Providers[0].Options + for k := range authProviderCfg { + options = append(options, k) + } + } + + settings := &SettingsResponse{ + Auth: &Auth{ + Enabled: cfg.Auth.Enabled, + Options: options, + }, + BannerText: cfg.BannerText, + DefaultNamespace: cfg.DefaultNamespace, + ShowTemporalSystemNamespace: cfg.ShowTemporalSystemNamespace, + FeedbackURL: cfg.FeedbackURL, + NotifyOnNewVersion: cfg.NotifyOnNewVersion, + Codec: &CodecResponse{ + Endpoint: cfg.Codec.Endpoint, + PassAccessToken: cfg.Codec.PassAccessToken, + IncludeCredentials: cfg.Codec.IncludeCredentials, + DefaultErrorMessage: cfg.Codec.DefaultErrorMessage, + DefaultErrorLink: cfg.Codec.DefaultErrorLink, + }, + Version: version.UIVersion, + DisableWriteActions: cfg.DisableWriteActions, + WorkflowTerminateDisabled: cfg.WorkflowTerminateDisabled, + WorkflowCancelDisabled: cfg.WorkflowCancelDisabled, + WorkflowSignalDisabled: cfg.WorkflowSignalDisabled, + WorkflowUpdateDisabled: cfg.WorkflowUpdateDisabled, + WorkflowResetDisabled: cfg.WorkflowResetDisabled, + BatchActionsDisabled: cfg.BatchActionsDisabled, + StartWorkflowDisabled: cfg.StartWorkflowDisabled, + HideWorkflowQueryErrors: cfg.HideWorkflowQueryErrors, + RefreshWorkflowCountsDisabled: cfg.RefreshWorkflowCountsDisabled, + ActivityCommandsDisabled: cfg.ActivityCommandsDisabled, + } + + return c.JSON(http.StatusOK, settings) + } +} + +func errorHandler( + ctx context.Context, + mux *runtime.ServeMux, + marshaler runtime.Marshaler, + w http.ResponseWriter, + r *http.Request, + err error, +) { + // Convert the error using serviceerror. The result does not conform to Google + // gRPC status directly (it conforms to gogo gRPC status), but Err() does + // based on internal code reading. However, Err() uses Google proto Any + // which our marshaler is not expecting. So instead we are embedding similar + // logic to runtime.DefaultHTTPProtoErrorHandler in here but with gogo + // support. We don't implement custom content type marshaler or trailers at + // this time. + s := serviceerror.ToStatus(err) + w.Header().Set("Content-Type", marshaler.ContentType(struct{}{})) + + buf, merr := marshaler.Marshal(s.Proto()) + if merr != nil { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(`{"code": 13, "message": "failed to marshal error message"}`)) + return + } + + w.WriteHeader(runtime.HTTPStatusFromCode(s.Code())) + _, _ = w.Write(buf) +} + +func getTemporalClientMux(c echo.Context, temporalConn *grpc.ClientConn, apiMiddleware []Middleware) (*runtime.ServeMux, error) { + var muxOpts []runtime.ServeMuxOption + for _, m := range apiMiddleware { + muxOpts = append(muxOpts, m(c)) + } + + tMux := runtime.NewServeMux( + append(muxOpts, + withMarshaler(), + version.WithVersionHeader(c), + runtime.WithUnescapingMode(runtime.UnescapingModeAllExceptReserved), + // This is necessary to get error details properly + // marshalled in unary requests. + runtime.WithErrorHandler(errorHandler), + )..., + ) + + if wfErr := workflowservice.RegisterWorkflowServiceHandler(context.Background(), tMux, temporalConn); wfErr != nil { + return nil, wfErr + } + + if opErr := operatorservice.RegisterOperatorServiceHandler(context.Background(), tMux, temporalConn); opErr != nil { + return nil, opErr + } + return tMux, nil +} + +func withMarshaler() runtime.ServeMuxOption { + return runtime.WithMarshalerOption(runtime.MIMEWildcard, temporalProtoMarshaler{ + contentType: "application/json", + mOpts: temporalproto.CustomJSONMarshalOptions{ + Indent: " ", + }, + }) +} diff --git a/server/server/api/marshaler.go b/server/server/api/marshaler.go new file mode 100644 index 000000000..254a3250a --- /dev/null +++ b/server/server/api/marshaler.go @@ -0,0 +1,101 @@ +package api + +import ( + "encoding/json" + "io" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "go.temporal.io/api/temporalproto" + "google.golang.org/protobuf/proto" +) + +type temporalProtoMarshaler struct { + contentType string + mOpts temporalproto.CustomJSONMarshalOptions + uOpts temporalproto.CustomJSONUnmarshalOptions +} + +type temporalProtoEncoder struct { + mOpts temporalproto.CustomJSONMarshalOptions + writer io.Writer + json *json.Encoder +} + +type temporalProtoDecoder struct { + uOpts temporalproto.CustomJSONUnmarshalOptions + reader io.Reader + json *json.Decoder +} + +func (p temporalProtoMarshaler) Marshal(v any) ([]byte, error) { + if m, ok := v.(proto.Message); ok { + return p.mOpts.Marshal(m) + } + + if p.mOpts.Indent != "" { + return json.MarshalIndent(v, "", p.mOpts.Indent) + } + + return json.Marshal(v) +} + +func (p temporalProtoMarshaler) Unmarshal(data []byte, v interface{}) error { + if m, ok := v.(proto.Message); ok { + return p.uOpts.Unmarshal(data, m) + } + + return json.Unmarshal(data, v) +} + +func (p temporalProtoMarshaler) NewDecoder(r io.Reader) runtime.Decoder { + return temporalProtoDecoder{ + p.uOpts, + r, + json.NewDecoder(r), + } +} + +func (p temporalProtoMarshaler) NewEncoder(w io.Writer) runtime.Encoder { + return temporalProtoEncoder{ + p.mOpts, + w, + json.NewEncoder(w), + } +} + +func (p temporalProtoMarshaler) ContentType(_ any) string { + return p.contentType +} + +func (d temporalProtoDecoder) Decode(v any) error { + m, ok := v.(proto.Message) + if !ok { + return d.json.Decode(v) + } + + var bs json.RawMessage + if err := d.json.Decode(&bs); err != nil { + return err + } + + return d.uOpts.Unmarshal([]byte(bs), m) +} + +func (e temporalProtoEncoder) Encode(v any) error { + m, ok := v.(proto.Message) + if !ok { + return e.json.Encode(v) + } + + bs, err := e.mOpts.Marshal(m) + if err != nil { + return err + } + + _, err = e.writer.Write(bs) + if err != nil { + return err + } + _, err = e.writer.Write([]byte{'\n'}) + return err +} diff --git a/server/server/api/middleware.go b/server/server/api/middleware.go new file mode 100644 index 000000000..d1fa5f169 --- /dev/null +++ b/server/server/api/middleware.go @@ -0,0 +1,32 @@ +// The MIT License +// +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package api + +import ( + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/labstack/echo/v4" +) + +type Middleware func(c echo.Context) runtime.ServeMuxOption diff --git a/server/server/api/rawhistory.go b/server/server/api/rawhistory.go new file mode 100644 index 000000000..15d40da74 --- /dev/null +++ b/server/server/api/rawhistory.go @@ -0,0 +1,181 @@ +package api + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/labstack/echo/v4" + "go.temporal.io/api/common/v1" + "go.temporal.io/api/workflowservice/v1" + "golang.org/x/net/context" + "google.golang.org/grpc" +) + +const WorkflowRawHistoryUrl = "/namespaces/:namespace/workflows/:workflow/run/:runid/history.json" + +func WorkflowRawHistoryHandler(service IWorkflowService) echo.HandlerFunc { + return func(c echo.Context) error { + // url decode workflow param + value := c.Param("workflow") + decodedValue, err := url.QueryUnescape(value) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid workflow ID: %s", value)) + } + + // Set headers for JSON streaming + c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + c.Response().WriteHeader(http.StatusOK) + + // // Stream the history events + return NewRawHistory(). + SetIDs( + c.Param("namespace"), + decodedValue, + c.Param("runid"), + ). + SetContext(c.Request().Context()). + SetWriter(c.Response().Writer). + SetWorkflowService(service). + StreamEvents() + } +} + +type IWorkflowService interface { + GetWorkflowExecutionHistory( + ctx context.Context, + in *workflowservice.GetWorkflowExecutionHistoryRequest, + opts ...grpc.CallOption, + ) (*workflowservice.GetWorkflowExecutionHistoryResponse, error) +} + +func NewRawHistory() *RawHistory { + return &RawHistory{} +} + +type RawHistory struct { + namespace string + workflowID string + runID string + + writtenCount int + + context context.Context + writer http.ResponseWriter + encoder *json.Encoder + service IWorkflowService +} + +func (rh *RawHistory) SetIDs( + namespace string, + workflowID string, + runID string, +) *RawHistory { + rh.namespace = namespace + rh.workflowID = workflowID + rh.runID = runID + + return rh +} + +func (rh *RawHistory) SetContext(c context.Context) *RawHistory { + rh.context = c + + return rh +} + +func (rh *RawHistory) SetWriter(w http.ResponseWriter) *RawHistory { + rh.writer = w + rh.encoder = json.NewEncoder(rh.writer) + + return rh +} + +func (rh *RawHistory) SetWorkflowService(service IWorkflowService) *RawHistory { + rh.service = service + + return rh +} + +func (rh *RawHistory) StreamEvents() error { + // Write opening of response object + fmt.Fprint(rh.writer, "[") + + // Write the history events + err := rh.streamEvents() + if err != nil { + rh.handleError(err) + } + + fmt.Fprint(rh.writer, "]") + return nil +} + +// streamEvents handles the actual event streaming with pagination +func (rh *RawHistory) streamEvents() error { + var nextPageToken []byte + isFirstEvent := true + + writer := rh.writer + encoder := rh.encoder + + for { + // Get a page of history + response, err := rh.fetchPage(nextPageToken) + if err != nil { + return err + } + + // Stream each event in this page + for _, event := range response.History.Events { + // Add comma separator between events + if !isFirstEvent { + fmt.Fprint(writer, ",") + } else { + isFirstEvent = false + } + + // Write event directly to response + if err := encoder.Encode(event); err != nil { + return err + } + + // Flush to ensure streaming + writer.(http.Flusher).Flush() + rh.writtenCount++ + } + + // Check if we're done + nextPageToken = response.NextPageToken + if len(nextPageToken) == 0 { + break + } + } + + return nil +} + +// fetchHistoryPage gets a single page of workflow history +func (rh *RawHistory) fetchPage( + nextPageToken []byte, +) (*workflowservice.GetWorkflowExecutionHistoryResponse, error) { + request := &workflowservice.GetWorkflowExecutionHistoryRequest{ + Namespace: rh.namespace, + Execution: &common.WorkflowExecution{ + WorkflowId: rh.workflowID, + RunId: rh.runID, + }, + NextPageToken: nextPageToken, + } + + return rh.service.GetWorkflowExecutionHistory(rh.context, request) +} + +func (rh *RawHistory) handleError(err error) { + if rh.writtenCount > 0 { + fmt.Fprint(rh.writer, ",") + } + + fmt.Fprintf(rh.writer, `{"error": "%s"}`, err) +} diff --git a/server/server/api/rawhistory_test.go b/server/server/api/rawhistory_test.go new file mode 100644 index 000000000..7fadb6f3f --- /dev/null +++ b/server/server/api/rawhistory_test.go @@ -0,0 +1,173 @@ +package api_test + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/temporalio/ui-server/v2/server/api" + + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "go.temporal.io/api/history/v1" + "go.temporal.io/api/workflowservice/v1" + "google.golang.org/grpc" +) + +// MockWorkflowService is a mock implementation of IWorkflowService +type MockWorkflowService struct { + mock.Mock +} + +func (m *MockWorkflowService) GetWorkflowExecutionHistory( + ctx context.Context, + req *workflowservice.GetWorkflowExecutionHistoryRequest, + opts ...grpc.CallOption, +) (*workflowservice.GetWorkflowExecutionHistoryResponse, error) { + args := m.Called(ctx, req) + return args.Get(0).(*workflowservice.GetWorkflowExecutionHistoryResponse), args.Error(1) +} + +func TestWorkflowRawHistoryHandler_HappyPath(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + c.SetParamNames("namespace", "workflow", "runid") + c.SetParamValues("test-namespace", "test-workflow", "test-runid") + + mockService := new(MockWorkflowService) + mockService.On("GetWorkflowExecutionHistory", mock.Anything, mock.Anything). + Return(&workflowservice.GetWorkflowExecutionHistoryResponse{ + History: &history.History{ + Events: []*history.HistoryEvent{ + {EventId: 1, EventType: 1}, + }, + }, + }, nil) + + handler := api.WorkflowRawHistoryHandler(mockService) + + err := handler(c) + + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, rec.Code) + assert.JSONEq(t, `[{"Attributes": null, "event_id":1, "event_type":1}]`, rec.Body.String()) + mockService.AssertExpectations(t) +} + +func TestWorkflowRawHistoryHandler_URLDecoding(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + // URL encoded workflow ID (this is "test/workflow+id") + c.SetParamNames("namespace", "workflow", "runid") + c.SetParamValues("test-namespace", "test%2Fworkflow%2Bid", "test-runid") + + mockService := new(MockWorkflowService) + // Check that the decoded workflow ID is passed to the service + mockService.On("GetWorkflowExecutionHistory", mock.Anything, mock.MatchedBy(func(req *workflowservice.GetWorkflowExecutionHistoryRequest) bool { + return req.Execution.WorkflowId == "test/workflow+id" + })).Return(&workflowservice.GetWorkflowExecutionHistoryResponse{ + History: &history.History{ + Events: []*history.HistoryEvent{ + {EventId: 1, EventType: 1}, + }, + }, + }, nil) + + handler := api.WorkflowRawHistoryHandler(mockService) + + err := handler(c) + + assert.NoError(t, err) + assert.Equal(t, http.StatusOK, rec.Code) + mockService.AssertExpectations(t) +} + +func TestWorkflowRawHistoryHandler_InvalidURLEncoding(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + // Invalid URL encoding with incomplete percent encoding + c.SetParamNames("namespace", "workflow", "runid") + c.SetParamValues("test-namespace", "invalid%2", "test-runid") + + mockService := new(MockWorkflowService) + // The service should not be called because decoding fails + handler := api.WorkflowRawHistoryHandler(mockService) + + err := handler(c) + + // Error should be returned + httpErr, ok := err.(*echo.HTTPError) + assert.True(t, ok) + assert.Equal(t, http.StatusBadRequest, httpErr.Code) + assert.Contains(t, httpErr.Message, "Invalid workflow ID") + // No calls to the service should be made + mockService.AssertNotCalled(t, "GetWorkflowExecutionHistory") +} + +func TestRawHistory_StreamEvents_ErrorHandling(t *testing.T) { + mockService := new(MockWorkflowService) + mockService.On("GetWorkflowExecutionHistory", mock.Anything, mock.Anything). + Return(&workflowservice.GetWorkflowExecutionHistoryResponse{ + History: &history.History{ + Events: []*history.HistoryEvent{ + {EventId: 1, EventType: 1}, + }, + }, + }, fmt.Errorf("fetch error")) + + rec := httptest.NewRecorder() + rh := api.NewRawHistory(). + SetIDs("test-namespace", "test-workflow", "test-runid"). + SetContext(context.Background()). + SetWriter(rec). + SetWorkflowService(mockService) + + err := rh.StreamEvents() + + assert.NoError(t, err) + assert.JSONEq(t, `[{"error": "fetch error"}]`, rec.Body.String()) + mockService.AssertExpectations(t) +} + +func TestRawHistory_StreamEvents_HappyPath(t *testing.T) { + mockService := new(MockWorkflowService) + mockService.On("GetWorkflowExecutionHistory", mock.Anything, mock.Anything). + Return(&workflowservice.GetWorkflowExecutionHistoryResponse{ + History: &history.History{ + Events: []*history.HistoryEvent{ + {EventId: 1, EventType: 1}, + {EventId: 2, EventType: 5}, + }, + }, + NextPageToken: nil, + }, nil) + + rec := httptest.NewRecorder() + rh := api.NewRawHistory(). + SetIDs("test-namespace", "test-workflow", "test-runid"). + SetContext(context.Background()). + SetWriter(rec). + SetWorkflowService(mockService) + + err := rh.StreamEvents() + + assert.NoError(t, err) + assert.JSONEq( + t, + `[{"Attributes": null, "event_id":1,"event_type":1},{"Attributes": null, "event_id":2,"event_type":5}]`, + rec.Body.String(), + ) + mockService.AssertExpectations(t) +} diff --git a/server/server/auth/auth.go b/server/server/auth/auth.go new file mode 100644 index 000000000..e4eba3149 --- /dev/null +++ b/server/server/auth/auth.go @@ -0,0 +1,133 @@ +// The MIT License +// +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package auth + +import ( + "encoding/base64" + "encoding/json" + "errors" + "net/http" + "strconv" + "time" + "unicode/utf8" + + "github.com/labstack/echo/v4" + "github.com/temporalio/ui-server/v2/server/config" +) + +const ( + AuthorizationExtrasHeader = "authorization-extras" + cookieLen = 4000 +) + +func SetUser(c echo.Context, user *User) error { + if user.OAuth2Token == nil { + return errors.New("no OAuth2Token") + } + + userR := UserResponse{ + AccessToken: user.OAuth2Token.AccessToken, + } + + if user.IDToken != nil { + userR.IDToken = user.IDToken.RawToken + } + + if user.IDToken.Claims != nil { + userR.Name = user.IDToken.Claims.Name + userR.Email = user.IDToken.Claims.Email + userR.Picture = user.IDToken.Claims.Picture + } + + b, err := json.Marshal(userR) + if err != nil { + return errors.New("unable to serialize user data") + } + + s := base64.StdEncoding.EncodeToString(b) + parts := splitCookie(s) + + for i, p := range parts { + cookie := &http.Cookie{ + Name: "user" + strconv.Itoa(i), + Value: p, + MaxAge: int(time.Minute.Seconds()), + Secure: c.Request().TLS != nil, + HttpOnly: false, + Path: "/", + SameSite: http.SameSiteStrictMode, + } + c.SetCookie(cookie) + } + + return nil +} + +// ValidateAuthHeaderExists validates that the autorization header exists if auth is enabled. +// User autorization should be done in the frontend by claim-mapper and authorizer plugins. +// See https://docs.temporal.io/security#authentication +func ValidateAuthHeaderExists(c echo.Context, cfgProvider *config.ConfigProviderWithRefresh) error { + cfg, err := cfgProvider.GetConfig() + if err != nil { + return err + } + + isEnabled := cfg.Auth.Enabled + if !isEnabled { + return nil + } + + token := c.Request().Header.Get(echo.HeaderAuthorization) + if token == "" { + return echo.NewHTTPError(http.StatusUnauthorized, "unauthorized") + } + + // Handle token swapping for OIDC providers that require ID token as Bearer + if len(cfg.Auth.Providers) > 0 && cfg.Auth.Providers[0].UseIDTokenAsBearer { + idToken := c.Request().Header.Get(AuthorizationExtrasHeader) + if idToken != "" { + // Replace the Authorization header with ID token + c.Request().Header.Set(echo.HeaderAuthorization, "Bearer "+idToken) + // Remove the Authorization-Extras header to avoid confusion + c.Request().Header.Del(AuthorizationExtrasHeader) + } + } + + return nil +} + +func splitCookie(val string) []string { + splits := []string{} + + var l, r int + for l, r = 0, cookieLen; r < len(val); l, r = r, r+cookieLen { + for !utf8.RuneStart(val[r]) { + r-- + } + splits = append(splits, val[l:r]) + } + splits = append(splits, val[l:]) + return splits +} diff --git a/server/server/auth/auth_test.go b/server/server/auth/auth_test.go new file mode 100644 index 000000000..5e45e3482 --- /dev/null +++ b/server/server/auth/auth_test.go @@ -0,0 +1,219 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package auth_test + +import ( + _ "embed" + "net/http/httptest" + "testing" + + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" + "github.com/temporalio/ui-server/v2/server/auth" + "github.com/temporalio/ui-server/v2/server/config" + "golang.org/x/oauth2" +) + +// func TestSetUser(t *testing.T) { +// user := auth.User{ +// OAuth2Token: &oauth2.Token{ +// AccessToken: "XXX.YYY.ZZZ", +// }, +// IDToken: &auth.IDToken{ +// RawToken: "MMM.JJJ.NNN", +// Claims: auth.Claims{ +// Email: "test@email.com", +// EmailVerified: true, +// Name: "test-name", +// Picture: "test-picture", +// }, +// }, +// } + +// html = auth.SetUser(nil, &user) + +// assert.Contains(t, string(html), fmt.Sprintf(metaT, "access-token", user.OAuth2Token.AccessToken)) +// assert.Contains(t, string(html), fmt.Sprintf(metaT, "id-token", user.IDToken.RawToken)) +// assert.Contains(t, string(html), fmt.Sprintf(metaT, "user-name", user.IDToken.Claims.Name)) +// assert.Contains(t, string(html), fmt.Sprintf(metaT, "user-email", user.IDToken.Claims.Email)) +// assert.Contains(t, string(html), fmt.Sprintf(metaT, "user-picture", user.IDToken.Claims.Picture)) +// } + +func TestSetUser(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(echo.GET, "/", nil) + rec := httptest.NewRecorder() + + tests := map[string]struct { + user auth.User + ctx echo.Context + wantErr bool + }{ + "user empty": { + user: auth.User{}, + ctx: e.NewContext(req, rec), + wantErr: true, + }, + "user set": { + user: auth.User{ + OAuth2Token: &oauth2.Token{ + AccessToken: "XXX.YYY.ZZZ", + }, + IDToken: &auth.IDToken{ + RawToken: "MMM.JJJ.NNN", + Claims: &auth.Claims{ + Email: "test@email.com", + EmailVerified: true, + Name: "test-name", + Picture: "test-picture", + }, + }, + }, + ctx: e.NewContext(req, rec), + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + err := auth.SetUser(tt.ctx, &tt.user) + cookies := tt.ctx.Cookies() + + if tt.wantErr { + assert.Error(t, err) + assert.Empty(t, cookies) + return + } else { + assert.NoError(t, err) + setCookie := tt.ctx.Response().Header().Get(echo.HeaderSetCookie) + assert.Contains(t, setCookie, "user0") + } + }) + } +} + +func TestValidateAuthHeaderExists(t *testing.T) { + tests := []struct { + name string + authEnabled bool + useIDTokenAsBearer bool + authHeader string + authExtrasHeader string + expectedAuthHeader string + expectedError bool + }{ + { + name: "auth disabled - no validation", + authEnabled: false, + authHeader: "", + expectedError: false, + }, + { + name: "auth enabled - no auth header", + authEnabled: true, + authHeader: "", + expectedError: true, + }, + { + name: "auth enabled - with auth header", + authEnabled: true, + authHeader: "Bearer access-token", + expectedError: false, + }, + { + name: "useIDTokenAsBearer disabled - headers unchanged", + authEnabled: true, + useIDTokenAsBearer: false, + authHeader: "Bearer access-token", + authExtrasHeader: "id-token", + expectedAuthHeader: "Bearer access-token", + expectedError: false, + }, + { + name: "useIDTokenAsBearer enabled - swap tokens", + authEnabled: true, + useIDTokenAsBearer: true, + authHeader: "Bearer access-token", + authExtrasHeader: "id-token", + expectedAuthHeader: "Bearer id-token", + expectedError: false, + }, + { + name: "useIDTokenAsBearer enabled - no extras header", + authEnabled: true, + useIDTokenAsBearer: true, + authHeader: "Bearer access-token", + authExtrasHeader: "", + expectedAuthHeader: "Bearer access-token", + expectedError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup + e := echo.New() + req := httptest.NewRequest(echo.GET, "/", nil) + if tt.authHeader != "" { + req.Header.Set(echo.HeaderAuthorization, tt.authHeader) + } + if tt.authExtrasHeader != "" { + req.Header.Set("authorization-extras", tt.authExtrasHeader) + } + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + // Create config provider + cfg := &config.Config{ + Auth: config.Auth{ + Enabled: tt.authEnabled, + }, + } + if tt.useIDTokenAsBearer { + cfg.Auth.Providers = []config.AuthProvider{ + { + UseIDTokenAsBearer: true, + }, + } + } + cfgProvider, err := config.NewConfigProviderWithRefresh(cfg) + assert.NoError(t, err) + + // Execute + err = auth.ValidateAuthHeaderExists(c, cfgProvider) + + // Assert + if tt.expectedError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + if tt.expectedAuthHeader != "" { + assert.Equal(t, tt.expectedAuthHeader, c.Request().Header.Get(echo.HeaderAuthorization)) + } + // When useIDTokenAsBearer is enabled and extras header exists, it should be removed + if tt.useIDTokenAsBearer && tt.authExtrasHeader != "" { + assert.Empty(t, c.Request().Header.Get("authorization-extras")) + } + } + }) + } +} diff --git a/server/server/auth/oidc.go b/server/server/auth/oidc.go new file mode 100644 index 000000000..d863dc0d7 --- /dev/null +++ b/server/server/auth/oidc.go @@ -0,0 +1,110 @@ +// The MIT License +// +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package auth + +import ( + "context" + "net/http" + + "github.com/coreos/go-oidc/v3/oidc" + "github.com/labstack/echo/v4" + "golang.org/x/oauth2" +) + +type User struct { + OAuth2Token *oauth2.Token + IDToken *IDToken +} + +type UserResponse struct { + AccessToken string + IDToken string + Name string + Email string + Picture string +} + +type IDToken struct { + RawToken string + Claims *Claims +} + +type Claims struct { + Email string `json:"email"` + EmailVerified bool `json:"email_verified"` + Name string `json:"name"` + Picture string `json:"picture"` +} + +func ExchangeCode(ctx context.Context, r *http.Request, config *oauth2.Config, provider *oidc.Provider) (*User, error) { + state, err := r.Cookie("state") + if err != nil { + return nil, echo.NewHTTPError(http.StatusBadRequest, "State cookie is not set in request") + } + if r.URL.Query().Get("state") != state.Value { + return nil, echo.NewHTTPError(http.StatusBadRequest, "State cookie did not match") + } + + oauth2Token, err := config.Exchange(ctx, r.URL.Query().Get("code")) + if err != nil { + return nil, echo.NewHTTPError(http.StatusInternalServerError, "Unable to exchange token: "+err.Error()) + } + + rawIDToken, ok := oauth2Token.Extra("id_token").(string) + if !ok { + return nil, echo.NewHTTPError(http.StatusInternalServerError, "No id_token field in oauth2 token.") + } + oidcConfig := &oidc.Config{ + ClientID: config.ClientID, + } + verifier := provider.Verifier(oidcConfig) + idToken, err := verifier.Verify(ctx, rawIDToken) + if err != nil { + return nil, echo.NewHTTPError(http.StatusInternalServerError, "Unable to verify ID Token: "+err.Error()) + } + + nonce, err := r.Cookie("nonce") + if err != nil { + return nil, echo.NewHTTPError(http.StatusBadRequest, "Nonce is not provided") + } + if idToken.Nonce != nonce.Value { + return nil, echo.NewHTTPError(http.StatusBadRequest, "Nonce did not match") + } + + var claims Claims + if err := idToken.Claims(&claims); err != nil { + return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + + user := User{ + OAuth2Token: oauth2Token, + IDToken: &IDToken{ + RawToken: rawIDToken, + Claims: &claims, + }, + } + + return &user, nil +} diff --git a/server/server/config/auth.go b/server/server/config/auth.go new file mode 100644 index 000000000..484a09f6c --- /dev/null +++ b/server/server/config/auth.go @@ -0,0 +1,68 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package config + +import ( + "errors" +) + +// Validate validates the persistence config +func (c *Auth) Validate() error { + if c == nil { + return nil + } + + if !c.Enabled { + return nil + } + + for _, p := range c.Providers { + if err := p.validate(); err != nil { + return err + } + } + + return nil +} + +func (c *AuthProvider) validate() error { + if c == nil { + return nil + } + + if c.ProviderURL == "" { + return errors.New("auth provider url is not") + } + + if c.ClientID == "" { + return errors.New("auth client id is not set") + } + + if c.CallbackURL == "" { + return errors.New("auth callback url is not set") + } + + return nil +} diff --git a/server/server/config/config.go b/server/server/config/config.go new file mode 100644 index 000000000..c7370a0a4 --- /dev/null +++ b/server/server/config/config.go @@ -0,0 +1,156 @@ +// The MIT License +// +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package config + +import ( + "errors" + "time" +) + +type ( + // Config contains the configuration for the UI server + Config struct { + TemporalGRPCAddress string `yaml:"temporalGrpcAddress"` + Host string `yaml:"host"` + Port int `yaml:"port"` + PublicPath string `yaml:"publicPath"` + TLS TLS `yaml:"tls"` + Auth Auth `yaml:"auth"` + EnableUI bool `yaml:"enableUi"` + CloudUI bool `yaml:"cloudUi"` + UIAssetPath string `yaml:"uiAssetPath"` + BannerText string `yaml:"bannerText"` + CORS CORS `yaml:"cors"` + DefaultNamespace string `yaml:"defaultNamespace"` + FeedbackURL string `yaml:"feedbackUrl"` + NotifyOnNewVersion bool `yaml:"notifyOnNewVersion"` + // Show temporal-system namespace in namespace selector + ShowTemporalSystemNamespace bool `yaml:"showTemporalSystemNamespace"` + // How often to reload the config + RefreshInterval time.Duration `yaml:"refreshInterval"` + Codec Codec `yaml:"codec"` + DisableWriteActions bool `yaml:"disableWriteActions"` + // Discrete configuration for Workflow Actions in the UI + WorkflowTerminateDisabled bool `yaml:"workflowTerminateDisabled"` + WorkflowCancelDisabled bool `yaml:"workflowCancelDisabled"` + WorkflowSignalDisabled bool `yaml:"workflowSignalDisabled"` + WorkflowUpdateDisabled bool `yaml:"workflowUpdateDisabled"` + WorkflowResetDisabled bool `yaml:"workflowResetDisabled"` + // Whether bulk/batch actions are enabled in the UI + BatchActionsDisabled bool `yaml:"batchActionsDisabled"` + // Whether start workflow is enabled in the UI + StartWorkflowDisabled bool `yaml:"startWorkflowDisabled"` + // Whether to hide server errors for workflow queries in UI + HideWorkflowQueryErrors bool `yaml:"hideWorkflowQueryErrors"` + // Whether to disable refreshing workflow counts in UI + RefreshWorkflowCountsDisabled bool `yaml:"refreshWorkflowCountsDisabled"` + // Whether to disable activity commands in the UI + ActivityCommandsDisabled bool `yaml:"activityCommandsDisabled"` + // Forward specified HTTP headers from HTTP API requests to Temporal gRPC backend + ForwardHeaders []string `yaml:"forwardHeaders"` + HideLogs bool `yaml:"hideLogs"` + } + + CORS struct { + AllowOrigins []string `yaml:"allowOrigins"` + UnsafeAllowAllOrigins bool `yaml:"unsafeAllowAllOrigins"` + // CookieInsecure allows CSRF cookie to be sent to servers that the browser considers + // unsecured. Useful for cases where the connection is secured via VPN rather than + // HTTPS directly. + CookieInsecure bool `yaml:"cookieInsecure"` + } + + TLS struct { + CaFile string `yaml:"caFile"` + CertFile string `yaml:"certFile"` + KeyFile string `yaml:"keyFile"` + CaData string `yaml:"caData"` + CertData string `yaml:"certData"` + KeyData string `yaml:"keyData"` + EnableHostVerification bool `yaml:"enableHostVerification"` + ServerName string `yaml:"serverName"` + } + + Auth struct { + // Enabled - UI checks this first before reading your provider config + Enabled bool `yaml:"enabled"` + // A list of auth providers. Currently enables only the first provider in the list. + Providers []AuthProvider `yaml:"providers"` + } + + AuthProvider struct { + // Label - optional label for the provider + Label string `yaml:"label"` + // Type of the auth provider. Only OIDC is supported today + Type string `yaml:"type"` + // OIDC .well-known/openid-configuration URL, ex. https://accounts.google.com/ + ProviderURL string `yaml:"providerUrl"` + // IssuerUrl - optional. Needed only when differs from the auth provider URL + IssuerUrl string `yaml:"issuerUrl"` + ClientID string `yaml:"clientId"` + ClientSecret string `yaml:"clientSecret"` + // Scopes for auth. Typically [openid, profile, email] + Scopes []string `yaml:"scopes"` + // CallbackURL - URL for the callback URL, ex. https://localhost:8080/sso/callback + CallbackURL string `yaml:"callbackUrl"` + // Options added as URL query params when redirecting to auth provider. Can be used to configure custom auth flows such as Auth0 invitation flow. + Options map[string]interface{} `yaml:"options"` + // UseIDTokenAsBearer - Use ID token instead of access token as Bearer in Authorization header + UseIDTokenAsBearer bool `yaml:"useIdTokenAsBearer"` + } + + Codec struct { + Endpoint string `yaml:"endpoint"` + PassAccessToken bool `yaml:"passAccessToken"` + IncludeCredentials bool `yaml:"includeCredentials"` + DefaultErrorMessage string `yaml:"defaultErrorMessage"` + DefaultErrorLink string `yaml:"defaultErrorLink"` + } + + Filesystem struct { + Path string `yaml:"path"` + } +) + +// Validate validates this config +func (c *Config) Validate() error { + if c.TemporalGRPCAddress == "" { + return errors.New("temporal frontend gRPC address is not set") + } + + if err := c.Auth.Validate(); err != nil { + return err + } + + return nil +} + +// Ensure that *Config implements ConfigProvider interface. +var _ ConfigProvider = &Config{} + +// GetConfig implements ConfigProvider. +func (c *Config) GetConfig() (*Config, error) { + return c, nil +} diff --git a/server/server/config/config_provider.go b/server/server/config/config_provider.go new file mode 100644 index 000000000..9076b7d7f --- /dev/null +++ b/server/server/config/config_provider.go @@ -0,0 +1,30 @@ +// The MIT License +// +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package config + +type ( + // ConfigProvider serves as a common interface to read UI server configuration. + ConfigProvider interface { + GetConfig() (*Config, error) + } +) diff --git a/server/server/config/config_provider_with_refresh.go b/server/server/config/config_provider_with_refresh.go new file mode 100644 index 000000000..5b0d8c09b --- /dev/null +++ b/server/server/config/config_provider_with_refresh.go @@ -0,0 +1,101 @@ +// The MIT License +// +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package config + +import ( + "log" + "sync" + "time" +) + +func NewConfigProviderWithRefresh(cfgProvider ConfigProvider) (*ConfigProviderWithRefresh, error) { + cfg, err := cfgProvider.GetConfig() + if err != nil { + return nil, err + } + + cfgRefresh := &ConfigProviderWithRefresh{ + cache: cfg, + provider: cfgProvider, + refreshInterval: cfg.RefreshInterval, + } + cfgRefresh.initialize() + + return cfgRefresh, nil +} + +type ConfigProviderWithRefresh struct { + sync.RWMutex + + cache *Config + provider ConfigProvider + refreshInterval time.Duration + + ticker *time.Ticker + stop chan bool +} + +func (r *ConfigProviderWithRefresh) GetConfig() (*Config, error) { + r.RLock() + defer r.RUnlock() + return r.cache, nil +} + +func (s *ConfigProviderWithRefresh) initialize() { + if s.refreshInterval != 0 { + s.stop = make(chan bool) + s.ticker = time.NewTicker(s.refreshInterval) + go s.refreshConfig() + } +} + +func (s *ConfigProviderWithRefresh) refreshConfig() { + for { + select { + case <-s.stop: + break + case <-s.ticker.C: + } + + newConfig, err := s.provider.GetConfig() + if err != nil { + log.Printf("unable to load new UI server configuration: %s", err) + continue + } + + log.Printf("loaded new UI server configuration") + s.Lock() + s.cache = newConfig + s.Unlock() + } +} + +func (s *ConfigProviderWithRefresh) Close() { + if s.ticker != nil { + s.ticker.Stop() + } + if s.stop != nil { + s.stop <- true + close(s.stop) + } +} diff --git a/server/server/cors/cors.go b/server/server/cors/cors.go new file mode 100644 index 000000000..bb9df7abb --- /dev/null +++ b/server/server/cors/cors.go @@ -0,0 +1,119 @@ +// The MIT License +// +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package cors + +import ( + "net/http" + "strings" + + "github.com/labstack/echo/v4" + "github.com/temporalio/ui-server/v2/server/config" +) + +// CORSConfig holds the configuration for CORS middleware +type CORSConfig struct { + AllowHeaders []string + AllowCredentials bool + ConfigProvider config.ConfigProvider +} + +// CORSMiddleware creates a performant CORS middleware that dynamically handles +// unsafe allow all origins configuration +func CORSMiddleware(config CORSConfig) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + cfg, err := config.ConfigProvider.GetConfig() + if err != nil { + return next(c) + } + + // Early return if unsafe allow all origins is not enabled + if !cfg.CORS.UnsafeAllowAllOrigins { + // Use static allowed origins + return handleStaticCORS(c, next, cfg.CORS.AllowOrigins, config) + } + + // Handle dynamic CORS for unsafe allow all origins + return handleDynamicCORS(c, next, config) + } + } +} + +// handleStaticCORS handles standard CORS with predefined allowed origins +func handleStaticCORS(c echo.Context, next echo.HandlerFunc, allowOrigins []string, config CORSConfig) error { + origin := c.Request().Header.Get("Origin") + + // Check if origin is in allowed list + allowed := false + for _, allowedOrigin := range allowOrigins { + if allowedOrigin == origin || allowedOrigin == "*" { + allowed = true + break + } + } + + if allowed { + setCORSHeaders(c, origin, config) + } + + // Handle preflight requests + if c.Request().Method == http.MethodOptions { + return c.NoContent(http.StatusNoContent) + } + + return next(c) +} + +// handleDynamicCORS handles unsafe allow all origins by using the request origin +func handleDynamicCORS(c echo.Context, next echo.HandlerFunc, config CORSConfig) error { + origin := c.Request().Header.Get("Origin") + + // Only set CORS headers if origin is present + if origin != "" { + setCORSHeaders(c, origin, config) + } + + // Handle preflight requests + if c.Request().Method == http.MethodOptions { + return c.NoContent(http.StatusNoContent) + } + + return next(c) +} + +// setCORSHeaders sets the appropriate CORS headers +func setCORSHeaders(c echo.Context, origin string, config CORSConfig) { + c.Response().Header().Set("Access-Control-Allow-Origin", origin) + + if config.AllowCredentials { + c.Response().Header().Set("Access-Control-Allow-Credentials", "true") + } + + if len(config.AllowHeaders) > 0 { + c.Response().Header().Set("Access-Control-Allow-Headers", strings.Join(config.AllowHeaders, ", ")) + } + + c.Response().Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") +} \ No newline at end of file diff --git a/server/server/cors/cors_test.go b/server/server/cors/cors_test.go new file mode 100644 index 000000000..ac77e9a60 --- /dev/null +++ b/server/server/cors/cors_test.go @@ -0,0 +1,162 @@ +// The MIT License +// +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package cors + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" + "github.com/temporalio/ui-server/v2/server/config" +) + +type mockConfigProvider struct { + cfg *config.Config +} + +func (m *mockConfigProvider) GetConfig() (*config.Config, error) { + return m.cfg, nil +} + +func TestCORSMiddleware_StaticOrigins(t *testing.T) { + cfg := &config.Config{ + CORS: config.CORS{ + AllowOrigins: []string{"https://example.com", "https://test.com"}, + UnsafeAllowAllOrigins: false, + }, + } + + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set("Origin", "https://example.com") + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + middleware := CORSMiddleware(CORSConfig{ + AllowHeaders: []string{"Content-Type"}, + AllowCredentials: true, + ConfigProvider: &mockConfigProvider{cfg: cfg}, + }) + + handler := middleware(func(c echo.Context) error { + return c.String(http.StatusOK, "test") + }) + + err := handler(c) + assert.NoError(t, err) + assert.Equal(t, "https://example.com", rec.Header().Get("Access-Control-Allow-Origin")) + assert.Equal(t, "true", rec.Header().Get("Access-Control-Allow-Credentials")) +} + +func TestCORSMiddleware_UnsafeAllowAllOrigins(t *testing.T) { + cfg := &config.Config{ + CORS: config.CORS{ + AllowOrigins: []string{"https://example.com"}, + UnsafeAllowAllOrigins: true, + }, + } + + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set("Origin", "https://malicious.com") + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + middleware := CORSMiddleware(CORSConfig{ + AllowHeaders: []string{"Content-Type"}, + AllowCredentials: true, + ConfigProvider: &mockConfigProvider{cfg: cfg}, + }) + + handler := middleware(func(c echo.Context) error { + return c.String(http.StatusOK, "test") + }) + + err := handler(c) + assert.NoError(t, err) + assert.Equal(t, "https://malicious.com", rec.Header().Get("Access-Control-Allow-Origin")) + assert.Equal(t, "true", rec.Header().Get("Access-Control-Allow-Credentials")) +} + +func TestCORSMiddleware_StaticOrigins_NotAllowed(t *testing.T) { + cfg := &config.Config{ + CORS: config.CORS{ + AllowOrigins: []string{"https://example.com"}, + UnsafeAllowAllOrigins: false, + }, + } + + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set("Origin", "https://malicious.com") + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + middleware := CORSMiddleware(CORSConfig{ + AllowHeaders: []string{"Content-Type"}, + AllowCredentials: true, + ConfigProvider: &mockConfigProvider{cfg: cfg}, + }) + + handler := middleware(func(c echo.Context) error { + return c.String(http.StatusOK, "test") + }) + + err := handler(c) + assert.NoError(t, err) + assert.Empty(t, rec.Header().Get("Access-Control-Allow-Origin")) +} + +func TestCORSMiddleware_PreflightRequest(t *testing.T) { + cfg := &config.Config{ + CORS: config.CORS{ + AllowOrigins: []string{"https://example.com"}, + UnsafeAllowAllOrigins: false, + }, + } + + e := echo.New() + req := httptest.NewRequest(http.MethodOptions, "/", nil) + req.Header.Set("Origin", "https://example.com") + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + middleware := CORSMiddleware(CORSConfig{ + AllowHeaders: []string{"Content-Type"}, + AllowCredentials: true, + ConfigProvider: &mockConfigProvider{cfg: cfg}, + }) + + handler := middleware(func(c echo.Context) error { + return c.String(http.StatusOK, "should not reach here") + }) + + err := handler(c) + assert.NoError(t, err) + assert.Equal(t, http.StatusNoContent, rec.Code) + assert.Equal(t, "https://example.com", rec.Header().Get("Access-Control-Allow-Origin")) +} \ No newline at end of file diff --git a/server/server/csrf/skipper.go b/server/server/csrf/skipper.go new file mode 100644 index 000000000..38bfbdeae --- /dev/null +++ b/server/server/csrf/skipper.go @@ -0,0 +1,31 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package csrf + +import ( + "github.com/labstack/echo/v4" +) + +func SkipOnAuthorizationHeader(c echo.Context) bool { + return c.Request().Header.Get(echo.HeaderAuthorization) != "" +} diff --git a/server/server/csrf/skipper_test.go b/server/server/csrf/skipper_test.go new file mode 100644 index 000000000..ade1e7157 --- /dev/null +++ b/server/server/csrf/skipper_test.go @@ -0,0 +1,62 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package csrf + +import ( + "net/http/httptest" + "testing" + + "github.com/labstack/echo/v4" + + "github.com/stretchr/testify/assert" +) + +func TestSkipOnAuthorizationHeader(t *testing.T) { + e := echo.New() + req := httptest.NewRequest(echo.GET, "/api/v1/settings", nil) + rec := httptest.NewRecorder() + + tests := map[string]struct { + ctx echo.Context + authorizationHeader string + expected bool + }{ + "authorization empty": { + ctx: e.NewContext(req, rec), + authorizationHeader: "", + expected: false, + }, + "authorization set": { + ctx: e.NewContext(req, rec), + authorizationHeader: "Bearer xxx", + expected: true, + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + tt.ctx.Request().Header.Set(echo.HeaderAuthorization, tt.authorizationHeader) + assert.Equal(t, SkipOnAuthorizationHeader(tt.ctx), tt.expected) + }) + } +} diff --git a/server/server/headers/headers.go b/server/server/headers/headers.go new file mode 100644 index 000000000..0d40ad2d0 --- /dev/null +++ b/server/server/headers/headers.go @@ -0,0 +1,50 @@ +// The MIT License +// +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package headers + +import ( + "context" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/labstack/echo/v4" + "github.com/temporalio/ui-server/v2/server/api" + "google.golang.org/grpc/metadata" +) + +func WithForwardHeaders(headers []string) api.Middleware { + return func(c echo.Context) runtime.ServeMuxOption { + return runtime.WithMetadata( + func(ctx context.Context, req *http.Request) metadata.MD { + md := metadata.MD{} + for _, header := range headers { + if x := c.Request().Header.Get(header); x != "" { + md.Append(header, x) + } + } + + return md + }, + ) + } +} diff --git a/server/server/route/api.go b/server/server/route/api.go new file mode 100644 index 000000000..6460e6d01 --- /dev/null +++ b/server/server/route/api.go @@ -0,0 +1,98 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package route + +import ( + "fmt" + "net/http" + "strings" + + "github.com/labstack/echo/v4" + "go.temporal.io/api/workflowservice/v1" + + "github.com/temporalio/ui-server/v2/server/api" + "github.com/temporalio/ui-server/v2/server/config" +) + +func DisableWriteMiddleware(cfgProvider *config.ConfigProviderWithRefresh) echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + cfg, err := cfgProvider.GetConfig() + if err != nil { + return c.JSON(http.StatusInternalServerError, err) + } + + if c.Request().Method == http.MethodGet { + return next(c) + } + + if cfg.DisableWriteActions { + path := c.Request().URL.Path + method := c.Request().Method + + if method == http.MethodPost && strings.HasPrefix(path, "/api/v1/namespaces/") && + strings.Contains(path, "/workflows/") && strings.Contains(path, "/query/") { + return next(c) + } + + return echo.ErrMethodNotAllowed + } + + return next(c) + } + } +} + +// SetAPIRoutes sets api routes +func SetAPIRoutes(e *echo.Echo, cfgProvider *config.ConfigProviderWithRefresh, apiMiddleware []api.Middleware) error { + + route := e.Group("/api/v1") + route.GET("/settings", api.GetSettings(cfgProvider)) + + writeControlMiddleware := DisableWriteMiddleware(cfgProvider) + conn, err := api.CreateGRPCConnection(cfgProvider) + if err != nil { + return fmt.Errorf("Failed to create gRPC connection to Temporal server: %w", err) + } + + route.GET( + api.WorkflowRawHistoryUrl, + api.WorkflowRawHistoryHandler(workflowservice.NewWorkflowServiceClient(conn)), + writeControlMiddleware, + ) + + route.Match([]string{"GET", "POST", "PUT", "PATCH", "DELETE"}, "/*", api.TemporalAPIHandler(cfgProvider, apiMiddleware, conn), writeControlMiddleware) + + // New api paths with removed prefix. Need to figure out how to handle when ui and ui server are on same host + // e.GET("/settings", api.GetSettings(cfgProvider)) + // e.GET("/cluster", api.TemporalAPIHandler(cfgProvider, apiMiddleware, conn), writeControlMiddleware) + // e.GET("/system-info", api.TemporalAPIHandler(cfgProvider, apiMiddleware, conn), writeControlMiddleware) + + // cluster := e.Group("/cluster") + // cluster.Match([]string{"GET", "POST", "PUT", "PATCH", "DELETE"}, "/*", api.TemporalAPIHandler(cfgProvider, apiMiddleware, conn), writeControlMiddleware) + + // namespaces := e.Group("/namespaces") + // namespaces.Match([]string{"GET", "POST", "PUT", "PATCH", "DELETE"}, "/*", api.TemporalAPIHandler(cfgProvider, apiMiddleware, conn), writeControlMiddleware) + + return nil +} diff --git a/server/server/route/auth.go b/server/server/route/auth.go new file mode 100644 index 000000000..21cab9972 --- /dev/null +++ b/server/server/route/auth.go @@ -0,0 +1,233 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package route + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "log" + "net/http" + "net/url" + "time" + + "github.com/coreos/go-oidc/v3/oidc" + "github.com/gorilla/securecookie" + "github.com/labstack/echo/v4" + "github.com/temporalio/ui-server/v2/server/auth" + "github.com/temporalio/ui-server/v2/server/config" + "golang.org/x/net/context" + "golang.org/x/oauth2" +) + +// SetAuthRoutes sets routes used by auth +func SetAuthRoutes(e *echo.Echo, cfgProvider *config.ConfigProviderWithRefresh) { + ctx := context.Background() + serverCfg, err := cfgProvider.GetConfig() + if err != nil { + fmt.Printf("unable to get auth config: %s\n", err) + } + + if !serverCfg.Auth.Enabled { + return + } + + if len(serverCfg.Auth.Providers) == 0 { + log.Fatal(`auth providers configuration is empty. Configure an auth provider or disable auth`) + } + + providerCfg := serverCfg.Auth.Providers[0] // only single provider is currently supported + + if len(providerCfg.IssuerUrl) > 0 { + ctx = oidc.InsecureIssuerURLContext(ctx, providerCfg.IssuerUrl) + } + provider, err := oidc.NewProvider(ctx, providerCfg.ProviderURL) + if err != nil { + log.Fatal(err) + } + + oauthCfg := oauth2.Config{ + ClientID: providerCfg.ClientID, + ClientSecret: providerCfg.ClientSecret, + Endpoint: provider.Endpoint(), + RedirectURL: providerCfg.CallbackURL, + Scopes: providerCfg.Scopes, + } + + api := e.Group("/auth") + api.GET("/sso", authenticate(&oauthCfg, providerCfg.Options)) + api.GET("/sso/callback", authenticateCb(ctx, &oauthCfg, provider)) + api.GET("/sso_callback", authenticateCb(ctx, &oauthCfg, provider)) // compatibility with UI v1 +} + +func authenticate(config *oauth2.Config, options map[string]interface{}) func(echo.Context) error { + return func(c echo.Context) error { + state, err := randString() + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err) + } + nonce, err := randNonce(c) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, err) + } + setCallbackCookie(c, "state", state) + setCallbackCookie(c, "nonce", nonce) + + opts := []oauth2.AuthCodeOption{ + oidc.Nonce(nonce), + } + for k, v := range options { + var value string + if vStr, ok := v.(string); ok { + value = vStr + } + + // Some options, ex Auth0 invitation code, may be undefined in config as they are unknowns beforehand + // These may come from outside, ex in an invitation email + vOverride := c.QueryParam(k) + if vOverride != "" { + value = vOverride + } + + opts = append(opts, oauth2.SetAuthURLParam(k, value)) + } + + url := config.AuthCodeURL(state, opts...) + + return c.Redirect(http.StatusFound, url) + } +} + +func authenticateCb(ctx context.Context, oauthCfg *oauth2.Config, provider *oidc.Provider) func(echo.Context) error { + return func(c echo.Context) error { + user, err := auth.ExchangeCode(ctx, c.Request(), oauthCfg, provider) + if err != nil { + return err + } + + err = auth.SetUser(c, user) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "unable to set user: "+err.Error()) + } + + nonceS, err := c.Request().Cookie("nonce") + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "nonce is not provided") + } + nonce, err := nonceFromString(nonceS.Value) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "nonce is invalid") + } + + returnUrl := nonce.ReturnURL + if returnUrl == "" { + returnUrl = "/" + } + + return c.Redirect(http.StatusSeeOther, returnUrl) + } +} + +func setCallbackCookie(c echo.Context, name, value string) { + // Explicitly expire pre v2.8.0 state and nonce cookies. + // As they had different path, they were not being cleared and in some cases result in "state did not match" error. + cookiePreV280 := &http.Cookie{ + Name: name, + Value: "", + MaxAge: -1, + Secure: c.Request().TLS != nil, + Path: "", + HttpOnly: true, + } + c.SetCookie(cookiePreV280) + + cookie := &http.Cookie{ + Name: name, + Value: value, + MaxAge: int(time.Hour.Seconds()), + Secure: c.Request().TLS != nil, + Path: "/", + HttpOnly: true, + } + c.SetCookie(cookie) +} + +func randString() (string, error) { + b := securecookie.GenerateRandomKey(16) + if b == nil { + return "", errors.New("unable to generate rand string for auth") + } + + return base64.RawURLEncoding.EncodeToString(b), nil +} + +func randNonce(c echo.Context) (string, error) { + v, err := randString() + if err != nil { + return "", err + } + + returnURL := c.QueryParam("returnUrl") + u, err := url.Parse(returnURL) + if err != nil { + return "", fmt.Errorf("invalid returnUrl: %w", err) + } + + // Reject redirects to other hosts, our use case only needs to handle local redirects. + if u.Host != "" && u.Host != c.Request().Host { + return "", fmt.Errorf("invalid returnUrl: does not match expected host %s", c.Request().Host) + } + + n := &Nonce{ + Nonce: v, + ReturnURL: returnURL, + } + + bytes, err := json.Marshal(n) + if err != nil { + return "", err + } + + return base64.RawURLEncoding.EncodeToString(bytes), nil +} + +func nonceFromString(nonce string) (*Nonce, error) { + var n Nonce + + bytes, err := base64.RawURLEncoding.DecodeString(nonce) + if err != nil { + return nil, err + } + + if err := json.Unmarshal(bytes, &n); err != nil { + return nil, err + } + + return &n, nil +} + +type Nonce struct { + Nonce string `json:"nonce"` + ReturnURL string `json:"return_url"` +} diff --git a/server/server/route/health.go b/server/server/route/health.go new file mode 100644 index 000000000..0d722613f --- /dev/null +++ b/server/server/route/health.go @@ -0,0 +1,35 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package route + +import ( + "net/http" + + "github.com/labstack/echo/v4" +) + +func SetHealthRoute(e *echo.Echo) { + e.GET("/healthz", func(c echo.Context) error { + return c.JSON(http.StatusOK, map[string]string{"status": "OK"}) + }) +} diff --git a/server/server/route/public_path.go b/server/server/route/public_path.go new file mode 100644 index 000000000..05c38e8a7 --- /dev/null +++ b/server/server/route/public_path.go @@ -0,0 +1,34 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package route + +import ( + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" +) + +func PublicPath(path string) echo.MiddlewareFunc { + return middleware.Rewrite(map[string]string{ + path + "/*": "/$1", + }) +} diff --git a/server/server/route/ui.go b/server/server/route/ui.go new file mode 100644 index 000000000..a3858e314 --- /dev/null +++ b/server/server/route/ui.go @@ -0,0 +1,328 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package route + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "fmt" + "html/template" + "io/fs" + "log" + "net/http" + "path" + "regexp" + "strings" + + "github.com/gomarkdown/markdown" + "github.com/gomarkdown/markdown/html" + "github.com/gomarkdown/markdown/parser" + + "github.com/labstack/echo/v4" +) + +// SetUIRoutes sets UI routes +func SetUIRoutes(e *echo.Echo, publicPath string, assets fs.FS) error { + assetsHandler := buildUIAssetsHandler(assets) + e.GET("/_app/*", assetsHandler) + e.GET("/css/*", assetsHandler) + e.GET("/codemirror/*", assetsHandler) + e.GET("/android*", assetsHandler) + e.GET("/apple*", assetsHandler) + e.GET("/banner*", assetsHandler) + e.GET("/favicon*", assetsHandler) + e.GET("/logo*", assetsHandler) + e.GET("/Temporal_Logo_Animation.gif", assetsHandler) + e.GET("/site.webmanifest", assetsHandler) + e.GET("/i18n/*", assetsHandler) + indexHandler, err := buildUIIndexHandler(publicPath, assets) + if err != nil { + return err + } + + e.GET("/*", indexHandler) + + return nil +} + +func removeCSPMeta(htmlStr string) string { + re := regexp.MustCompile(`(?i)]*http-equiv\s*=\s*["']content-security-policy["'][^>]*>`) + return re.ReplaceAllString(htmlStr, "") +} + +func buildUIIndexHandler(publicPath string, assets fs.FS) (echo.HandlerFunc, error) { + indexHTMLBytes, err := fs.ReadFile(assets, "index.html") + if err != nil { + return nil, err + } + if publicPath != "" { + indexHTML := string(indexHTMLBytes) + indexHTML = strings.ReplaceAll(indexHTML, "base: \"\"", fmt.Sprintf("base: \"%s\"", publicPath)) + indexHTML = strings.ReplaceAll(indexHTML, "\"/_app/", fmt.Sprintf("\"%s/_app/", publicPath)) + log.Printf("WARNING: CSP IS DISABLED WHEN USING PUBLIC PATH!") + indexHTML = removeCSPMeta(indexHTML) + indexHTMLBytes = []byte(indexHTML) + } + + return func(c echo.Context) (err error) { + return c.Stream(200, "text/html", bytes.NewBuffer(indexHTMLBytes)) + }, nil +} + +func buildUIAssetsHandler(assets fs.FS) echo.HandlerFunc { + handler := http.FileServer(http.FS(assets)) + return echo.WrapHandler(handler) +} + +func generateNonce() string { + bytes := make([]byte, 16) + if _, err := rand.Read(bytes); err != nil { + panic(err) + } + return hex.EncodeToString(bytes) +} + +// Generate CSP header +func generateCSP(nonce string) string { + return fmt.Sprintf( + "base-uri 'self'; default-src 'none'; style-src 'nonce-%s'; script-src 'nonce-%s'; frame-ancestors 'self'; form-action 'none'; sandbox allow-same-origin allow-popups allow-popups-to-escape-sandbox;", + nonce, + nonce, + ) +} + +// Process markdown content +func processMarkdown(content string) string { + // Create markdown parser with extensions + extensions := parser.CommonExtensions | parser.AutoHeadingIDs + p := parser.NewWithExtensions(extensions) + + // Parse markdown to AST + ast := p.Parse([]byte(content)) + + // Setup HTML renderer + opts := html.RendererOptions{ + Flags: html.CommonFlags | html.HrefTargetBlank, + } + renderer := html.NewRenderer(opts) + + // Render to HTML + return string(markdown.Render(ast, renderer)) +} + +// Template for the HTML page +const pageTemplate = ` + + + + Rendered Markdown + + + + + + +
+ {{.Content}} +
+ + +` + +func SetRenderRoute(e *echo.Echo, publicPath string) { + renderPath := path.Join(publicPath, "render") + + // Parse template once at startup + tmpl := template.Must(template.New("page").Parse(pageTemplate)) + + e.GET(renderPath, func(c echo.Context) error { + content := c.QueryParam("content") + theme := c.QueryParam("theme") + + // Process markdown to HTML + renderedHTML := processMarkdown(content) + + nonce := generateNonce() + + data := struct { + Content template.HTML + Nonce string + Theme string + CSS string + }{ + Content: template.HTML(renderedHTML), + Nonce: nonce, + Theme: theme, + CSS: `*, + body { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + vertical-align: baseline; + } + + body { + overscroll-behavior: none; + position: relative; + padding: 1rem; + white-space: pre-line; + font-family: sans-serif; + } + + h1 { + font-size: 2em; + } + + h2 { + font-size: 1.5em; + } + + h3 { + font-size: 1.17em; + } + + h4 { + font-size: 1em; + } + + h5 { + font-size: 0.83em; + } + + h6 { + font-size: 0.67em; + } + + blockquote, + q { + quotes: none; + } + blockquote:before, + blockquote:after, + q:before, + q:after { + content: ''; + content: none; + } + + table { + border-collapse: collapse; + border-spacing: 0; + } + + ul, + ol { + white-space: normal; + } + + li { + list-style-position: inside; + } + + li * { + display: inline; + } + + a { + gap: 0.5rem; + align-items: center; + border-radius: 0.25rem; + max-width: fit-content; + text-decoration: underline; + text-underline-offset: 2px; + cursor: pointer; + } + + blockquote { + padding-top: 0; + padding-bottom: 0; + padding-left: 0.5rem; + border-left: 4px solid; + border-left-color: #92a4c3; + background: #e8efff; + color: #121416; + } + + blockquote p { + font-size: 1.25rem; + line-height: 1.75rem; + } + + code { + font-family: monospace; + padding-top: 0.125rem; + padding-bottom: 0.125rem; + padding-left: 0.25rem; + padding-right: 0.25rem; + border-radius: 0.25rem; + background: #e8efff; + color: #121416; + } + + pre { + font-family: monospace; + padding: 0.25rem; + border-radius: 0.25rem; + background: #e8efff; + color: #121416; + } + + pre code { + padding: 0; + } + + body[data-theme='light'] { + background-color: #fff; + color: #121416; + } + + body[data-theme='light'] a { + color: #444ce7; + } + + body[data-theme='dark'] { + background-color: #141414; + color: #f8fafc; + } + + body[data-theme='dark'] a { + color: #8098f9; + }`, + } + + // Set headers + c.Response().Header().Set("Content-Type", "text/html") + c.Response().Header().Set("Content-Security-Policy", generateCSP(nonce)) + + err := tmpl.Execute(c.Response().Writer, data) + if err != nil { + return err + } + + return nil + }) +} diff --git a/server/server/rpc/rpc.go b/server/server/rpc/rpc.go new file mode 100644 index 000000000..8ff89e508 --- /dev/null +++ b/server/server/rpc/rpc.go @@ -0,0 +1,100 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package rpc + +import ( + "context" + "crypto/tls" + "fmt" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/backoff" + "google.golang.org/grpc/credentials" +) + +const ( + // DefaultServiceConfig is a default gRPC connection service config which enables DNS round robin between IPs. + // To use DNS resolver, a "dns:///" prefix should be applied to the hostPort. + // https://github.com/grpc/grpc/blob/master/doc/naming.md + DefaultServiceConfig = `{"loadBalancingConfig": [{"round_robin":{}}]}` + + // MaxBackoffDelay is a maximum interval between reconnect attempts. + MaxBackoffDelay = 10 * time.Second + + // minConnectTimeout is the minimum amount of time we are willing to give a connection to complete. + minConnectTimeout = 20 * time.Second +) + +// CreateGRPCConnection creates connection for gRPC calls +func CreateGRPCConnection(hostName string, tls *tls.Config) *grpc.ClientConn { + connection, err := Dial(hostName, tls) + + if err != nil { + fmt.Println("Failed to create gRPC connection") + } + + return connection +} + +// Dial creates a client connection to the given target with default options. +// The hostName syntax is defined in +// https://github.com/grpc/grpc/blob/master/doc/naming.md. +// e.g. to use dns resolver, a "dns:///" prefix should be applied to the target. +func Dial(hostName string, tlsConfig *tls.Config) (*grpc.ClientConn, error) { + // Default to insecure + grpcSecureOpt := grpc.WithInsecure() + if tlsConfig != nil { + grpcSecureOpt = grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)) + } + + // gRPC maintains connection pool inside grpc.ClientConn. + // This connection pool has auto reconnect feature. + // If connection goes down, gRPC will try to reconnect using exponential backoff strategy: + // https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md. + // Default MaxDelay is 120 seconds which is too high. + var cp = grpc.ConnectParams{ + Backoff: backoff.DefaultConfig, + MinConnectTimeout: minConnectTimeout, + } + cp.Backoff.MaxDelay = MaxBackoffDelay + + mb := 1024 * 1024 + maxPayloadSize := 64 * mb + + return grpc.Dial(hostName, + grpcSecureOpt, + grpc.WithChainUnaryInterceptor( + errorInterceptor), + grpc.WithDefaultServiceConfig(DefaultServiceConfig), + grpc.WithDisableServiceConfig(), + grpc.WithConnectParams(cp), + grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxPayloadSize)), + ) +} + +func errorInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + err := invoker(ctx, method, req, reply, cc, opts...) + // err = serviceerrors.FromStatus(status.Convert(err)) + return err +} diff --git a/server/server/rpc/tls.go b/server/server/rpc/tls.go new file mode 100644 index 000000000..382bb3b6a --- /dev/null +++ b/server/server/rpc/tls.go @@ -0,0 +1,273 @@ +// The MIT License +// +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package rpc + +import ( + "crypto/tls" + "crypto/x509" + "encoding/base64" + "errors" + "fmt" + "io" + "log" + "net" + "net/http" + "os" + "strings" + "sync" + "time" + + "github.com/temporalio/ui-server/v2/server/config" +) + +const ( + localHostPort = "127.0.0.1:7233" +) + +var netClient HttpGetter = &http.Client{ + Timeout: time.Second * 10, +} + +type HttpGetter interface { + Get(url string) (resp *http.Response, err error) +} + +type certLoader struct { + CertFile string + KeyFile string + cachedCert *tls.Certificate + lastModTime time.Time + lock sync.RWMutex +} + +func (l *certLoader) GetClientCertificate(_ *tls.CertificateRequestInfo) (*tls.Certificate, error) { + stat, err := os.Stat(l.KeyFile) + if err != nil { + l.lock.RLock() + existingCert := l.cachedCert + l.lock.RUnlock() + + if existingCert == nil { + return nil, fmt.Errorf("statting tls key file: %w", err) + } + + log.Printf("unable to stat tls key file, returning cached cert which may expire: %s", err) + return existingCert, nil + } + + l.lock.RLock() + if existingCert := l.cachedCert; existingCert != nil && stat.ModTime().Equal(l.lastModTime) { + l.lock.RUnlock() + log.Printf("tls cert unchanged on disk; returning cached cert") + return existingCert, nil + } + l.lock.RUnlock() + + // If the cert file and key file don't match, tls.LoadX509KeyPair will + // return an error. This will protect us from a race condition where the key + // file has been written but the cert file has not yet. We'll log the error + // but keep returning the previous cert until loading the new cert succeeds. + cert, err := tls.LoadX509KeyPair(l.CertFile, l.KeyFile) + + l.lock.Lock() + defer l.lock.Unlock() + if err != nil { + log.Printf("unable to load tls key pair, returning cached cert which may expire: %s", err) + return l.cachedCert, nil + } + log.Printf("loaded new tls key pair") + + l.cachedCert = &cert + l.lastModTime = stat.ModTime() + + return l.cachedCert, nil +} + +func CreateTLSConfig(address string, cfg *config.TLS) (*tls.Config, error) { + err := validateTLSConfig(cfg) + if err != nil { + return nil, err + } + + var tlsConfig *tls.Config + + // If we are given a server name, set the TLS server name for DNS resolution + if cfg.ServerName != "" { + host := cfg.ServerName + tlsConfig = NewTLSConfigForServer(host, cfg.EnableHostVerification) + } + + configureCertPool := cfg.CaFile != "" || cfg.CaData != "" + + // validateTLSConfig above ensures these two cases are mutually exclusive + configureKeyPairFromFile := cfg.CertFile != "" || cfg.KeyFile != "" + configureKeyPairFromBytes := cfg.CertData != "" || cfg.KeyData != "" + + if !configureCertPool && !configureKeyPairFromFile && !configureKeyPairFromBytes { + return tlsConfig, nil + } + + // Initialize tls.Config with address in case server name is not in configuration + if tlsConfig == nil { + hostPort := address + if hostPort == "" { + hostPort = localHostPort + } + // Ignoring error as we'll fail to dial anyway, and that will produce a meaningful error + host, _, _ := net.SplitHostPort(hostPort) + tlsConfig = NewTLSConfigForServer(host, cfg.EnableHostVerification) + } + + if configureCertPool { + caCertPool, err := loadCACert(cfg) + if err != nil { + log.Fatalf("Unable to load server CA certificate") + return nil, err + } + tlsConfig.RootCAs = caCertPool + } + + if configureKeyPairFromFile { + // Configure server to reload client cert from file if it changes on + // disk. + certLoader := &certLoader{ + CertFile: cfg.CertFile, + KeyFile: cfg.KeyFile, + lock: sync.RWMutex{}, + } + tlsConfig.GetClientCertificate = certLoader.GetClientCertificate + } + + if configureKeyPairFromBytes { + // Load key pair from provided bytes. + keyPair, err := loadKeyPair(cfg) + if err != nil { + log.Fatalf("Unable to load client certificate") + return nil, err + } + tlsConfig.Certificates = []tls.Certificate{keyPair} + } + + return tlsConfig, nil +} + +func loadCACert(cfg *config.TLS) (caPool *x509.CertPool, err error) { + pathOrUrl := cfg.CaFile + caData := cfg.CaData + + caPool = x509.NewCertPool() + var caBytes []byte + + if strings.HasPrefix(pathOrUrl, "http://") { + return nil, errors.New("HTTP is not supported for CA cert URLs. Provide HTTPS URL") + } + + if strings.HasPrefix(pathOrUrl, "https://") { + resp, err := netClient.Get(pathOrUrl) + if err != nil { + return nil, fmt.Errorf("unable to load CA cert from URL: %v", err) + } + defer resp.Body.Close() + caBytes, err = io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("unable to load CA cert from URL: %v", err) + } + + log.Printf("Loaded TLS CA cert from URL: %v", pathOrUrl) + } else if pathOrUrl != "" { + caBytes, err = os.ReadFile(pathOrUrl) + if err != nil { + return nil, fmt.Errorf("unable to load CA cert from file: %v", err) + } + log.Printf("Loaded TLS CA cert from file: %v", pathOrUrl) + } else if caData != "" { + caBytes, err = base64.StdEncoding.DecodeString(caData) + if err != nil { + return nil, fmt.Errorf("unable to decode CA cert from base64: %v", err) + } + log.Printf("Loaded CA cert from base64") + } + + if !caPool.AppendCertsFromPEM(caBytes) { + return nil, errors.New("unknown failure constructing cert pool for ca") + } + return caPool, nil +} + +func loadKeyPair(cfg *config.TLS) (tls.Certificate, error) { + var certBytes []byte + var keyBytes []byte + var err error + + keyBytes, err = base64.StdEncoding.DecodeString(cfg.KeyData) + if err != nil { + return tls.Certificate{}, fmt.Errorf("unable to decode TLS key from base64: %w", err) + } + log.Printf("Loaded TLS key from base64") + + certBytes, err = base64.StdEncoding.DecodeString(cfg.CertData) + if err != nil { + return tls.Certificate{}, fmt.Errorf("unable to decode TLS cert from base64: %w", err) + } + log.Printf("Loaded TLS cert from base64") + + keyPair, err := tls.X509KeyPair(certBytes, keyBytes) + if err != nil { + return tls.Certificate{}, fmt.Errorf("unable to generate x509 key pair: %w", err) + } + + return keyPair, err +} + +func NewEmptyTLSConfig() *tls.Config { + return &tls.Config{ + MinVersion: tls.VersionTLS12, + NextProtos: []string{ + "h2", + }, + } +} + +func NewTLSConfigForServer( + serverName string, + enableHostVerification bool, +) *tls.Config { + c := NewEmptyTLSConfig() + c.ServerName = serverName + c.InsecureSkipVerify = !enableHostVerification + return c +} + +func validateTLSConfig(cfg *config.TLS) error { + if cfg.CertFile != "" && cfg.CertData != "" { + return fmt.Errorf("cannot specify TLS cert file and cert data at the same time") + } + if cfg.KeyFile != "" && cfg.KeyData != "" { + return fmt.Errorf("cannot specify TLS key file and key data at the same time") + } + if cfg.CaFile != "" && cfg.CaData != "" { + return fmt.Errorf("cannot specify TLS CA file and CA data at the same time") + } + + return nil +} diff --git a/server/server/rpc/tls_cert_loader_test.go b/server/server/rpc/tls_cert_loader_test.go new file mode 100644 index 000000000..472d8daa7 --- /dev/null +++ b/server/server/rpc/tls_cert_loader_test.go @@ -0,0 +1,93 @@ +package rpc + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestCertLoader_ReloadsNewKeyPair(t *testing.T) { + // Use Go testing temporary directory for test files + dir := t.TempDir() + certPath := filepath.Join(dir, "cert.pem") + keyPath := filepath.Join(dir, "key.pem") + + // Write initial certificate and key + certPEM1, keyPEM1 := generateCertKeyPair(t, "initial") + assert.NoError(t, os.WriteFile(certPath, certPEM1, 0644)) + assert.NoError(t, os.WriteFile(keyPath, keyPEM1, 0644)) + + // Initialize the loader + loader := &certLoader{CertFile: certPath, KeyFile: keyPath} + + // First load should read the initial pair + loaded1, err := loader.GetClientCertificate(nil) + assert.NoError(t, err) + assert.NotNil(t, loaded1) + + // Compare against a direct X509KeyPair parse + expect1, err := tls.X509KeyPair(certPEM1, keyPEM1) + assert.NoError(t, err) + assert.Equal(t, expect1.Certificate, loaded1.Certificate) + + // Wait to ensure file modification time will differ + time.Sleep(500 * time.Millisecond) + + // Overwrite with a new certificate and key + certPEM2, keyPEM2 := generateCertKeyPair(t, "updated") + assert.NoError(t, os.WriteFile(certPath, certPEM2, 0644)) + assert.NoError(t, os.WriteFile(keyPath, keyPEM2, 0644)) + + // Second load should pick up the updated pair + loaded2, err := loader.GetClientCertificate(nil) + assert.NoError(t, err) + assert.NotNil(t, loaded2) + + expect2, err := tls.X509KeyPair(certPEM2, keyPEM2) + assert.NoError(t, err) + + // Compare the loaded certificate with the expected one + assert.Equal(t, expect2.Certificate, loaded2.Certificate) + + // Ensure the loader did not return the old certificate + assert.NotEqual(t, expect1.Certificate, loaded2.Certificate) +} + +// generateCertKeyPair creates a self-signed certificate and private key for testing. +func generateCertKeyPair(t *testing.T, commonName string) (certPEM, keyPEM []byte) { + // Generate a private key + key, err := rsa.GenerateKey(rand.Reader, 2048) + assert.NoError(t, err) + // Create a certificate template + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + assert.NoError(t, err) + tmpl := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{CommonName: commonName}, + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + IsCA: true, + BasicConstraintsValid: true, + } + // Self-sign the certificate + derBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &key.PublicKey, key) + assert.NoError(t, err) + // PEM encode the certificate and key + certPEM = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + keyPEM = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) + return certPEM, keyPEM +} + diff --git a/server/server/server.go b/server/server/server.go new file mode 100644 index 000000000..30cb0cfba --- /dev/null +++ b/server/server/server.go @@ -0,0 +1,163 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package server + +import ( + "fmt" + "io/fs" + "net/http" + "os" + + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" + + "github.com/temporalio/ui-server/v2/server/api" + "github.com/temporalio/ui-server/v2/server/auth" + "github.com/temporalio/ui-server/v2/server/config" + "github.com/temporalio/ui-server/v2/server/cors" + "github.com/temporalio/ui-server/v2/server/csrf" + "github.com/temporalio/ui-server/v2/server/headers" + "github.com/temporalio/ui-server/v2/server/route" + "github.com/temporalio/ui-server/v2/server/server_options" + + "github.com/temporalio/ui-server/v2/ui" +) + +type ( + // Server ui server. + Server struct { + httpServer *echo.Echo + options *server_options.ServerOptions + cfgProvider *config.ConfigProviderWithRefresh + } +) + +// NewServer returns a new instance of server that serves one or many services. +func NewServer(opts ...server_options.ServerOption) *Server { + authMiddleware := server_options.WithAPIMiddleware(([]api.Middleware{ + headers.WithForwardHeaders( + []string{ + // NOTE: Authorization header is forwarded by grpc-gateway + auth.AuthorizationExtrasHeader, + "Caller-Type", + }), + })) + + opts = append(opts, authMiddleware) + + serverOpts := server_options.NewServerOptions(opts) + cfgProvider, err := config.NewConfigProviderWithRefresh(serverOpts.ConfigProvider) + if err != nil { + panic(err) + } + cfg, err := cfgProvider.GetConfig() + if err != nil { + panic(err) + } + + e := echo.New() + e.HideBanner = cfg.HideLogs + e.HidePort = cfg.HideLogs + + // Middleware + if !cfg.HideLogs { + e.Use(middleware.Logger()) + } + e.Use(middleware.Recover()) + e.Use(middleware.Gzip()) + e.Use(cors.CORSMiddleware(cors.CORSConfig{ + AllowHeaders: []string{ + echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, + echo.HeaderXCSRFToken, echo.HeaderAuthorization, auth.AuthorizationExtrasHeader, + "Caller-Type", + }, + AllowCredentials: true, + ConfigProvider: cfgProvider, + })) + e.Use(middleware.Secure()) + e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{ + CookiePath: "/", + CookieHTTPOnly: false, + CookieSameSite: http.SameSiteStrictMode, + CookieSecure: !cfg.CORS.CookieInsecure, + Skipper: csrf.SkipOnAuthorizationHeader, + })) + + e.Pre(route.PublicPath(cfg.PublicPath)) + route.SetHealthRoute(e) + route.SetAPIRoutes(e, cfgProvider, serverOpts.APIMiddleware) + route.SetAuthRoutes(e, cfgProvider) + if cfg.EnableUI { + var assets fs.FS + if cfg.UIAssetPath != "" { + assets = os.DirFS(cfg.UIAssetPath) + } else { + assets, err = ui.Assets() + if err != nil { + panic(err) + } + + dir := "local" + if cfg.CloudUI { + dir = "cloud" + } + + assets, err = fs.Sub(assets, dir) + if err != nil { + panic(err) + } + } + route.SetUIRoutes(e, cfg.PublicPath, assets) + route.SetRenderRoute(e, cfg.PublicPath) + } + + s := &Server{ + httpServer: e, + options: serverOpts, + cfgProvider: cfgProvider, + } + return s +} + +// Start UI server. +func (s *Server) Start() error { + s.httpServer.Logger.Info("Starting UI server...") + cfg, err := s.cfgProvider.GetConfig() + if err != nil { + return err + } + + address := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) + if err := s.httpServer.Start(address); err != http.ErrServerClosed { + s.httpServer.Logger.Fatal(err) + } + return nil +} + +// Stop UI server. +func (s *Server) Stop() { + s.httpServer.Logger.Info("Stopping UI server...") + if err := s.httpServer.Close(); err != nil { + s.httpServer.Logger.Warn(err) + } +} diff --git a/server/server/server_options/apply_func_container.go b/server/server/server_options/apply_func_container.go new file mode 100644 index 000000000..acc1f9690 --- /dev/null +++ b/server/server/server_options/apply_func_container.go @@ -0,0 +1,41 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package server_options + +type ( + applyFuncContainer struct { + applyInternal func(*ServerOptions) + } +) + +func (fso *applyFuncContainer) apply(s *ServerOptions) { + fso.applyInternal(s) +} + +func newApplyFuncContainer(apply func(option *ServerOptions)) *applyFuncContainer { + return &applyFuncContainer{ + applyInternal: apply, + } +} diff --git a/server/server/server_options/server_option.go b/server/server/server_options/server_option.go new file mode 100644 index 000000000..a7dc74a75 --- /dev/null +++ b/server/server/server_options/server_option.go @@ -0,0 +1,50 @@ +// The MIT License +// +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package server_options + +import ( + "github.com/temporalio/ui-server/v2/server/api" + "github.com/temporalio/ui-server/v2/server/config" +) + +type ( + ServerOption interface { + apply(*ServerOptions) + } +) + +// WithConfigProvider supplies the config for the UI server +func WithConfigProvider(cfgProvider config.ConfigProvider) ServerOption { + return newApplyFuncContainer(func(s *ServerOptions) { + s.ConfigProvider = cfgProvider + }) +} + +// WithAPIMiddleware supplies API middleware +func WithAPIMiddleware(middleware []api.Middleware) ServerOption { + return newApplyFuncContainer(func(s *ServerOptions) { + s.APIMiddleware = append(s.APIMiddleware, middleware...) + }) +} diff --git a/server/server/server_options/server_options.go b/server/server/server_options/server_options.go new file mode 100644 index 000000000..aa886878f --- /dev/null +++ b/server/server/server_options/server_options.go @@ -0,0 +1,47 @@ +// The MIT License +// +// Copyright (c) 2022 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package server_options + +import ( + "github.com/temporalio/ui-server/v2/server/api" + "github.com/temporalio/ui-server/v2/server/config" +) + +type ( + ServerOptions struct { + ConfigProvider config.ConfigProvider + APIMiddleware []api.Middleware + } +) + +func NewServerOptions(opts []ServerOption) *ServerOptions { + so := &ServerOptions{ + // Set defaults here. + } + for _, opt := range opts { + opt.apply(so) + } + return so +} diff --git a/server/server/version/version.go b/server/server/version/version.go new file mode 100644 index 000000000..5ef8dacba --- /dev/null +++ b/server/server/version/version.go @@ -0,0 +1,32 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package version + +const ( + clientNameHeaderName = "client-name" + clientNameHeaderValue = "temporal-ui" + clientVersionHeaderName = "client-version" + UIVersion = "2.39.0" +) diff --git a/server/server/version/with_version_header.go b/server/server/version/with_version_header.go new file mode 100644 index 000000000..17e1ceec3 --- /dev/null +++ b/server/server/version/with_version_header.go @@ -0,0 +1,47 @@ +// The MIT License +// +// Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. +// +// Copyright (c) 2020 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package version + +import ( + "context" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/labstack/echo/v4" + "google.golang.org/grpc/metadata" +) + +func WithVersionHeader(c echo.Context) runtime.ServeMuxOption { + return runtime.WithMetadata( + func(ctx context.Context, req *http.Request) metadata.MD { + md := metadata.MD{} + + md.Append(clientNameHeaderName, clientNameHeaderValue) + md.Append(clientVersionHeaderName, UIVersion) + + return md + }, + ) +} diff --git a/server/ui/embed.go b/server/ui/embed.go new file mode 100644 index 000000000..7f6d58b59 --- /dev/null +++ b/server/ui/embed.go @@ -0,0 +1,13 @@ +package ui + +import ( + "embed" + "io/fs" +) + +//go:embed all:assets +var assets embed.FS + +func Assets() (fs.FS, error) { + return fs.Sub(assets, "assets") +} diff --git a/src/api.d.ts b/src/api.d.ts deleted file mode 100644 index 4134266a1..000000000 --- a/src/api.d.ts +++ /dev/null @@ -1,61 +0,0 @@ -type WorkflowsAPIRoutePath = - | 'workflows' - | 'workflows.archived' - | 'workflows.count' - | 'workflows.batch.terminate' - | 'workflows.batch.describe'; - -type WorkflowAPIRoutePath = - | 'workflow' - | 'workflow.terminate' - | 'workflow.cancel' - | 'events.ascending' - | 'events.descending' - | 'query'; - -type NamespaceAPIRoutePath = 'namespace'; - -type TaskQueueAPIRoutePath = 'task-queue'; -type ParameterlessAPIRoutePath = 'cluster' | 'settings' | 'user' | 'namespaces'; -type SchedulesAPIRoutePath = 'schedules'; -type ScheduleAPIRoutePath = 'schedule' | 'schedule.delete'; -type SearchAttributesRoutePath = 'search-attributes'; - -type APIRoutePath = - | ParameterlessAPIRoutePath - | ScheduleAPIRoutePath - | SchedulesAPIRoutePath - | SearchAttributesRoutePath - | TaskQueueAPIRoutePath - | WorkflowAPIRoutePath - | WorkflowsAPIRoutePath - | NamespaceAPIRoutePath; - -type APIRouteParameters = { - namespace: string; - workflowId: string; - scheduleId: string; - runId: string; - queue: string; - scheduleId: string; -}; - -type WorkflowListRouteParameters = Pick; -type NamespaceRouteParameters = Pick; -type ScheduleListRouteParameters = Pick; - -type WorkflowRouteParameters = Pick< - APIRouteParameters, - 'namespace' | 'workflowId' | 'runId' ->; - -type TaskQueueRouteParameters = Pick; - -type ValidWorkflowEndpoints = WorkflowsAPIRoutePath; - -type ValidWorkflowParameters = ArchiveFilterParameters | FilterParameters; - -type ScheduleRouteParameters = Pick< - APIRouteParameters, - 'namespace' | 'scheduleId' ->; diff --git a/src/app.css b/src/app.css index a4e9cb838..29ce0137b 100644 --- a/src/app.css +++ b/src/app.css @@ -1,33 +1,23 @@ -@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@100;200;300;400;500;600;700;800;900&display=swap'); -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap'); +@import '@fontsource-variable/inter'; + +@import '@fontsource/noto-sans-mono'; @import 'tailwindcss/base'; @import 'tailwindcss/components'; @import 'tailwindcss/utilities'; -*, -html, -body { - box-sizing: border-box; -} +@layer base { + *, + html, + body { + @apply box-border; + } -body { - font-family: Inter, sans-serif; - position: relative; -} - -h1, -h2, -h3, -h4, -h5, -h6, -titles, -labels { - font-family: Poppins, sans-serif; -} + body { + @apply body-normal surface-background relative overscroll-none; + } -.hljs { - max-height: 25em; - overflow-y: scroll; + input[type='search']::-webkit-search-cancel-button { + @apply hidden; + } } diff --git a/src/app.d.ts b/src/app.d.ts index d1c417818..1cc36746d 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -8,10 +8,10 @@ declare namespace App { interface Session {} - interface Stuff { - workflow?: WorkflowExecution; - settings: Settings; - workers?: GetPollersResponse; - cluster?: ClusterInformation; + interface PageData { + workflow?: import('$types').WorkflowExecution; + settings?: import('$types').Settings; + workers?: import('$types').GetPollersResponse; + cluster?: import('$types').ClusterInformation; } } diff --git a/src/app.html b/src/app.html index ed42b20bc..b616dc1de 100644 --- a/src/app.html +++ b/src/app.html @@ -1,4 +1,4 @@ - + diff --git a/src/env.d.ts b/src/env.d.ts index a72e77785..12b3e902b 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -4,8 +4,8 @@ type Vitest = import('vitest'); interface ImportMetaEnv { readonly VITE_TEMPORAL_UI_BUILD_TARGET: string; - readonly VITE_PUBLIC_PATH: string; readonly VITE_API: string; + readonly VITE_DARK_MODE: boolean; } interface ImportMeta { diff --git a/src/events.d.ts b/src/events.d.ts deleted file mode 100644 index be49694ac..000000000 --- a/src/events.d.ts +++ /dev/null @@ -1,189 +0,0 @@ -type HistoryEvent = import('$types').HistoryEvent; -type PendingActivityInfo = import('$types').PendingActivityInfo; -type PendingChildren = import('$types').PendingChildrenInfo; - -type EventRequestMetadata = { - namespace: string; - settings: Settings; - accessToken: string; -}; - -type EventWithMetadata = { - historyEvent: HistoryEvent; -} & EventRequestMetadata; - -type EventsWithMetadata = { - response: HistoryEvent[]; -} & EventRequestMetadata; - -type EventType = import('$lib/utilities/is-event-type').EventType; - -type ActivityType = import('$lib/utilities/is-event-type').ActivityType; -type TimerType = import('$lib/utilities/is-event-type').TimerType; -type SignalType = import('$lib/utilities/is-event-type').SignalType; -type MarkerType = import('$lib/utilities/is-event-type').MarkerType; -type ChildType = import('$lib/utilities/is-event-type').ChildType; - -type EventTypeCategory = - import('$lib/models/event-history/get-event-categorization').EventTypeCategory; - -type EventClassification = - import('$lib/utilities/get-event-classiciation').EventClassification; - -interface WorkflowEvent extends HistoryEvent { - id: string; - eventType: EventType; - attributes: EventAttribute; - timestamp: string; - classification: EventClassification; - category: EventTypeCategory; - name: EventType; -} - -type WorkflowEvents = WorkflowEvent[]; - -interface PendingActivity extends PendingActivityInfo { - id: typeof PendingActivityInfo.activityId; - state: 'Unspecified' | 'Scheduled' | 'Started' | 'CancelRequested'; - activityType: { name: string }; -} - -type PendingActivityWithMetadata = { - activity: PendingActivity; -} & EventRequestMetadata; - -type CommonEventKey = - | 'id' - | 'eventType' - | 'attributes' - | 'eventId' - | 'eventTime' - | 'version' - | 'taskId' - | 'timestamp' - | 'classification' - | 'category' - | 'name'; - -type CommonHistoryEvent = Pick; - -type EventAttributeKey = keyof Omit; -type EventAttribute = HistoryEvent[EventAttributeKey]; -type EventAttributesWithType = HistoryEvent[K] & { - type: K; -}; - -type EventWithAttributes = CommonHistoryEvent & - Pick & { attributes: EventAttributesWithType }; - -type ActivityEvent = ActivityTaskScheduledEvent & - ActivityTaskStartedEvent & - ActivityTaskCompletedEvent & - ActivityTaskFailedEvent & - ActivityTaskTimedOutEvent & - ActivityTaskCancelRequestedEvent & - ActivityTaskCanceledEvent; - -type TimerEvent = TimerCanceledEvent & TimerStartedEvent & TimerFiredEvent; - -type SignalEvent = SignalExternalWorkflowExecutionInitiatedEvent & - SignalExternalWorkflowExecutionFailedEvent & - WorkflowExecutionSignaledEvent; - -type MarkerEvent = MarkerRecordedEvent; - -type ChildEvent = StartChildWorkflowExecutionInitiatedEvent & - StartChildWorkflowExecutionFailedEvent & - ChildWorkflowExecutionStartedEvent & - ChildWorkflowExecutionCompletedEvent & - ChildWorkflowExecutionFailedEvent & - ChildWorkflowExecutionCanceledEvent & - ChildWorkflowExecutionTimedOutEvent & - ChildWorkflowExecutionTerminatedEvent; - -type EventView = 'feed' | 'compact' | 'json'; - -type FetchEventsResponse = { - events: WorkflowEvents; - eventGroups: EventGroups; -}; - -type IterableEvent = WorkflowEvent | EventGroup; -type IterableEvents = IterableEvent[]; - -type WorkflowExecutionStartedEvent = - EventWithAttributes<'workflowExecutionStartedEventAttributes'>; -type WorkflowExecutionCompletedEvent = - EventWithAttributes<'workflowExecutionCompletedEventAttributes'>; -type WorkflowExecutionFailedEvent = - EventWithAttributes<'workflowExecutionFailedEventAttributes'>; -type WorkflowExecutionTimedOutEvent = - EventWithAttributes<'workflowExecutionTimedOutEventAttributes'>; -type WorkflowTaskScheduledEvent = - EventWithAttributes<'workflowTaskScheduledEventAttributes'>; -type WorkflowTaskStartedEvent = - EventWithAttributes<'workflowTaskStartedEventAttributes'>; -type WorkflowTaskCompletedEvent = - EventWithAttributes<'workflowTaskCompletedEventAttributes'>; -type WorkflowTaskTimedOutEvent = - EventWithAttributes<'workflowTaskTimedOutEventAttributes'>; -type WorkflowTaskFailedEvent = - EventWithAttributes<'workflowTaskFailedEventAttributes'>; -type ActivityTaskScheduledEvent = - EventWithAttributes<'activityTaskScheduledEventAttributes'>; -type ActivityTaskStartedEvent = - EventWithAttributes<'activityTaskStartedEventAttributes'>; -type ActivityTaskCompletedEvent = - EventWithAttributes<'activityTaskCompletedEventAttributes'>; -type ActivityTaskFailedEvent = - EventWithAttributes<'activityTaskFailedEventAttributes'>; -type ActivityTaskTimedOutEvent = - EventWithAttributes<'activityTaskTimedOutEventAttributes'>; -type TimerStartedEvent = EventWithAttributes<'timerStartedEventAttributes'>; -type TimerFiredEvent = EventWithAttributes<'timerFiredEventAttributes'>; -type ActivityTaskCancelRequestedEvent = - EventWithAttributes<'activityTaskCancelRequestedEventAttributes'>; -type ActivityTaskCanceledEvent = - EventWithAttributes<'activityTaskCanceledEventAttributes'>; -type TimerCanceledEvent = EventWithAttributes<'timerCanceledEventAttributes'>; -type MarkerRecordedEvent = EventWithAttributes<'markerRecordedEventAttributes'>; -type WorkflowExecutionSignaledEvent = - EventWithAttributes<'workflowExecutionSignaledEventAttributes'>; -type WorkflowExecutionTerminatedEvent = - EventWithAttributes<'workflowExecutionTerminatedEventAttributes'>; -type WorkflowExecutionCancelRequestedEvent = - EventWithAttributes<'workflowExecutionCancelRequestedEventAttributes'>; -type WorkflowExecutionCanceledEvent = - EventWithAttributes<'workflowExecutionCanceledEventAttributes'>; -type RequestCancelExternalWorkflowExecutionInitiatedEvent = - EventWithAttributes<'requestCancelExternalWorkflowExecutionInitiatedEventAttributes'>; -type RequestCancelExternalWorkflowExecutionFailedEvent = - EventWithAttributes<'requestCancelExternalWorkflowExecutionFailedEventAttributes'>; -type ExternalWorkflowExecutionCancelRequestedEvent = - EventWithAttributes<'externalWorkflowExecutionCancelRequestedEventAttributes'>; -type WorkflowExecutionContinuedAsNewEvent = - EventWithAttributes<'workflowExecutionContinuedAsNewEventAttributes'>; -type StartChildWorkflowExecutionInitiatedEvent = - EventWithAttributes<'startChildWorkflowExecutionInitiatedEventAttributes'>; -type StartChildWorkflowExecutionFailedEvent = - EventWithAttributes<'startChildWorkflowExecutionFailedEventAttributes'>; -type ChildWorkflowExecutionStartedEvent = - EventWithAttributes<'childWorkflowExecutionStartedEventAttributes'>; -type ChildWorkflowExecutionCompletedEvent = - EventWithAttributes<'childWorkflowExecutionCompletedEventAttributes'>; -type ChildWorkflowExecutionFailedEvent = - EventWithAttributes<'childWorkflowExecutionFailedEventAttributes'>; -type ChildWorkflowExecutionCanceledEvent = - EventWithAttributes<'childWorkflowExecutionCanceledEventAttributes'>; -type ChildWorkflowExecutionTimedOutEvent = - EventWithAttributes<'childWorkflowExecutionTimedOutEventAttributes'>; -type ChildWorkflowExecutionTerminatedEvent = - EventWithAttributes<'childWorkflowExecutionTerminatedEventAttributes'>; -type SignalExternalWorkflowExecutionInitiatedEvent = - EventWithAttributes<'signalExternalWorkflowExecutionInitiatedEventAttributes'>; -type SignalExternalWorkflowExecutionFailedEvent = - EventWithAttributes<'signalExternalWorkflowExecutionFailedEventAttributes'>; -type ExternalWorkflowExecutionSignaledEvent = - EventWithAttributes<'externalWorkflowExecutionSignaledEventAttributes'>; -type UpsertWorkflowSearchAttributesEvent = - EventWithAttributes<'upsertWorkflowSearchAttributesEventAttributes'>; diff --git a/src/fixtures/all-event-types.json b/src/fixtures/all-event-types.json index 8dd93d9e5..610d375a0 100644 --- a/src/fixtures/all-event-types.json +++ b/src/fixtures/all-event-types.json @@ -100,13 +100,19 @@ "version": "0", "taskId": "29866296", "workflowTaskScheduledEventAttributes": { - "taskQueue": { "name": "canary-task-queue", "kind": "Normal" }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, "startToCloseTimeout": "20s", "attempt": 1 }, "attributes": { "type": "workflowTaskScheduledEventAttributes", - "taskQueue": { "name": "canary-task-queue", "kind": "Normal" }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, "startToCloseTimeout": "20 seconds", "attempt": 1 }, @@ -179,14 +185,18 @@ "upsertWorkflowSearchAttributesEventAttributes": { "workflowTaskCompletedEventId": "4", "searchAttributes": { - "indexedFields": { "TemporalChangeVersion": ["initial version-3"] } + "indexedFields": { + "TemporalChangeVersion": ["initial version-3"] + } } }, "attributes": { "type": "upsertWorkflowSearchAttributesEventAttributes", "workflowTaskCompletedEventId": "4", "searchAttributes": { - "indexedFields": { "TemporalChangeVersion": ["initial version-3"] } + "indexedFields": { + "TemporalChangeVersion": ["initial version-3"] + } } }, "category": "command", @@ -206,14 +216,21 @@ "change-id": { "payloads": [ { - "metadata": { "encoding": "anNvbi9wbGFpbg==" }, + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, "data": "ImluaXRpYWwgdmVyc2lvbiI=" } ] }, "version": { "payloads": [ - { "metadata": { "encoding": "anNvbi9wbGFpbg==" }, "data": "Mw==" } + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "Mw==" + } ] } }, @@ -228,14 +245,21 @@ "change-id": { "payloads": [ { - "metadata": { "encoding": "anNvbi9wbGFpbg==" }, + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, "data": "ImluaXRpYWwgdmVyc2lvbiI=" } ] }, "version": { "payloads": [ - { "metadata": { "encoding": "anNvbi9wbGFpbg==" }, "data": "Mw==" } + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "Mw==" + } ] } }, @@ -255,15 +279,22 @@ "version": "0", "taskId": "29866290", "workflowExecutionStartedEventAttributes": { - "workflowType": { "name": "workflow.advanced-visibility" }, + "workflowType": { + "name": "workflow.advanced-visibility" + }, "parentWorkflowNamespace": "canary", "parentWorkflowExecution": { "workflowId": "temporal.fixture.timed-out.workflow.id", "runId": "78cba607-189e-41fe-a31b-a2b6061babaa" }, "parentInitiatedEventId": "11", - "taskQueue": { "name": "canary-task-queue", "kind": "Normal" }, - "input": { "payloads": [1656683778738403300, "canary"] }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "input": { + "payloads": ["1656683778738403300", "canary"] + }, "workflowExecutionTimeout": "0s", "workflowRunTimeout": "1200s", "workflowTaskTimeout": "20s", @@ -281,10 +312,14 @@ "firstWorkflowTaskBackoff": "0s", "memo": null, "searchAttributes": { - "indexedFields": { "CustomKeywordField": "childWorkflowValue" } + "indexedFields": { + "CustomKeywordField": "childWorkflowValue" + } }, "prevAutoResetPoints": null, - "header": { "fields": {} }, + "header": { + "fields": {} + }, "parentInitiatedEventVersion": "0" }, "attributes": { @@ -296,8 +331,13 @@ "runId": "78cba607-189e-41fe-a31b-a2b6061babaa" }, "parentInitiatedEventId": "11", - "taskQueue": { "name": "canary-task-queue", "kind": "Normal" }, - "input": { "payloads": [1656683778738403300, "canary"] }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "input": { + "payloads": ["1656683778738403300", "canary"] + }, "workflowExecutionTimeout": "", "workflowRunTimeout": "20 minutes", "workflowTaskTimeout": "20 seconds", @@ -315,10 +355,14 @@ "firstWorkflowTaskBackoff": "", "memo": null, "searchAttributes": { - "indexedFields": { "CustomKeywordField": "childWorkflowValue" } + "indexedFields": { + "CustomKeywordField": "childWorkflowValue" + } }, "prevAutoResetPoints": null, - "header": { "fields": {} }, + "header": { + "fields": {} + }, "parentInitiatedEventVersion": "0" }, "classification": "Started", @@ -410,12 +454,19 @@ "taskId": "29867308", "activityTaskScheduledEventAttributes": { "activityId": "13", - "activityType": { "name": "activity.advanced-visibility" }, - "taskQueue": { "name": "canary-task-queue", "kind": "Normal" }, - "header": { "fields": {} }, + "activityType": { + "name": "activity.advanced-visibility" + }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "header": { + "fields": {} + }, "input": { "payloads": [ - 1656683782883152600, + "1656683782883152600", { "ID": "temporal.fixture.timed-out.workflow.id/workflow.advanced-visibility", "RunID": "445a17cb-6cb4-4508-bc59-187d08c6c6e2" @@ -439,11 +490,16 @@ "type": "activityTaskScheduledEventAttributes", "activityId": "13", "activityType": "activity.advanced-visibility", - "taskQueue": { "name": "canary-task-queue", "kind": "Normal" }, - "header": { "fields": {} }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "header": { + "fields": {} + }, "input": { "payloads": [ - 1656683782883152600, + "1656683782883152600", { "ID": "temporal.fixture.timed-out.workflow.id/workflow.advanced-visibility", "RunID": "445a17cb-6cb4-4508-bc59-187d08c6c6e2" @@ -475,7 +531,10 @@ "eventType": "TimerFired", "version": "0", "taskId": "29867299", - "timerFiredEventAttributes": { "timerId": "8", "startedEventId": "8" }, + "timerFiredEventAttributes": { + "timerId": "8", + "startedEventId": "8" + }, "attributes": { "type": "timerFiredEventAttributes", "timerId": "8", @@ -600,10 +659,14 @@ "runId": "3cbbf515-36da-43b9-a1f3-18a7ec031ddd" }, "signalName": "signalToTrigger", - "input": { "payloads": ["signalValue"] }, + "input": { + "payloads": ["signalValue"] + }, "control": "25", "childWorkflowOnly": false, - "header": { "fields": {} } + "header": { + "fields": {} + } }, "attributes": { "type": "signalExternalWorkflowExecutionInitiatedEventAttributes", @@ -614,10 +677,14 @@ "runId": "3cbbf515-36da-43b9-a1f3-18a7ec031ddd" }, "signalName": "signalToTrigger", - "input": { "payloads": ["signalValue"] }, + "input": { + "payloads": ["signalValue"] + }, "control": "25", "childWorkflowOnly": false, - "header": { "fields": {} } + "header": { + "fields": {} + } }, "classification": "Initiated", "category": "signal", @@ -633,16 +700,24 @@ "taskId": "32096693", "workflowExecutionSignaledEventAttributes": { "signalName": "signalBeforeReset", - "input": { "payloads": ["signalValue"] }, + "input": { + "payloads": ["signalValue"] + }, "identity": "history-service", - "header": { "fields": {} } + "header": { + "fields": {} + } }, "attributes": { "type": "workflowExecutionSignaledEventAttributes", "signalName": "signalBeforeReset", - "input": { "payloads": ["signalValue"] }, + "input": { + "payloads": ["signalValue"] + }, "identity": "history-service", - "header": { "fields": {} } + "header": { + "fields": {} + } }, "classification": "Signaled", "category": "signal", @@ -756,5 +831,121 @@ "id": "14", "name": "ActivityTaskTimedOut", "timestamp": "2022-07-01 UTC 14:00:29.94" + }, + "WorkflowExecutionUpdateAccepted": { + "eventId": "13", + "eventTime": "2023-09-11T13:29:00.588588Z", + "eventType": "WorkflowExecutionUpdateAccepted", + "version": "0", + "taskId": "1048706", + "workerMayIgnore": false, + "workflowExecutionUpdateAcceptedEventAttributes": { + "protocolInstanceId": "31440fef-27ba-4393-bb59-debabc23b77d", + "acceptedRequestMessageId": "31440fef-27ba-4393-bb59-debabc23b77d/request", + "acceptedRequestSequencingEventId": "10", + "acceptedRequest": { + "meta": { + "updateId": "31440fef-27ba-4393-bb59-debabc23b77d", + "identity": "29187@Alexs-MacBook-Pro.local@" + }, + "input": { + "header": { + "fields": {} + }, + "name": "set-to-account", + "args": { + "payloads": ["Piggy Bank"] + } + } + } + }, + "name": "WorkflowExecutionUpdateAccepted", + "id": "13", + "timestamp": "2023-09-11 UTC 13:29:00.58", + "category": "update", + "attributes": { + "type": "workflowExecutionUpdateAcceptedEventAttributes", + "protocolInstanceId": "31440fef-27ba-4393-bb59-debabc23b77d", + "acceptedRequestMessageId": "31440fef-27ba-4393-bb59-debabc23b77d/request", + "acceptedRequestSequencingEventId": "10", + "acceptedRequest": { + "meta": { + "updateId": "31440fef-27ba-4393-bb59-debabc23b77d", + "identity": "29187@MacBook-Pro.local@" + }, + "input": { + "header": { + "fields": {} + }, + "name": "set-to-account", + "args": { + "payloads": ["Piggy Bank"] + } + } + } + } + }, + "WorkflowExecutionUpdateCompleted": { + "eventId": "14", + "eventTime": "2023-09-11T13:29:00.588642Z", + "eventType": "WorkflowExecutionUpdateCompleted", + "version": "0", + "taskId": "1048707", + "workerMayIgnore": false, + "workflowExecutionUpdateCompletedEventAttributes": { + "meta": { + "updateId": "31440fef-27ba-4393-bb59-debabc23b77d", + "identity": "" + }, + "acceptedEventId": "13", + "outcome": { + "success": { + "payloads": [null] + } + } + }, + "name": "WorkflowExecutionUpdateCompleted", + "id": "14", + "timestamp": "2023-09-11 UTC 13:29:00.58", + "classification": "Completed", + "category": "update", + "attributes": { + "type": "workflowExecutionUpdateCompletedEventAttributes", + "meta": { + "updateId": "31440fef-27ba-4393-bb59-debabc23b77d", + "identity": "" + }, + "acceptedEventId": "13", + "outcome": { + "success": { + "payloads": [null] + } + } + } + }, + "WorkflowExecutionUpdateRequested": { + "eventId": "14", + "eventTime": "2023-09-11T13:29:00.588642Z", + "eventType": "WorkflowExecutionUpdateRequested", + "name": "WorkflowExecutionUpdateRequested", + "id": "14", + "timestamp": "2023-09-11 UTC 13:29:00.58", + "classification": "Completed", + "category": "update", + "attributes": { + "type": "workflowExecutionUpdateRequestedEventAttributes", + "origin": "UPDATE_REQUESTED_EVENT_ORIGIN_REAPPLY", + "request": { + "meta": { + "updateId": "31440fef-27ba-4393-bb59-debabc23b77d", + "identity": "29187@MacBook-Pro.local@" + }, + "input": { + "header": { + "fields": {} + } + } + } + } } } diff --git a/src/fixtures/event-groups.completed.json b/src/fixtures/event-groups.completed.json index 33bf839c2..9bf32ce6d 100644 --- a/src/fixtures/event-groups.completed.json +++ b/src/fixtures/event-groups.completed.json @@ -361,7 +361,7 @@ }, "input": { "payloads": [ - 1656707332859456300, + "1656707332859456300", { "ID": "temporal.fixture.completed.workflow.id", "RunID": "202dcff6-7f35-4c65-995c-bcadce524fb1" @@ -394,7 +394,7 @@ }, "input": { "payloads": [ - 1656707332859456300, + "1656707332859456300", { "ID": "temporal.fixture.completed.workflow.id", "RunID": "202dcff6-7f35-4c65-995c-bcadce524fb1" @@ -437,7 +437,7 @@ }, "input": { "payloads": [ - 1656707332859456300, + "1656707332859456300", { "ID": "temporal.fixture.completed.workflow.id", "RunID": "202dcff6-7f35-4c65-995c-bcadce524fb1" @@ -478,7 +478,7 @@ }, "input": { "payloads": [ - 1656707332859456300, + "1656707332859456300", { "ID": "temporal.fixture.completed.workflow.id", "RunID": "202dcff6-7f35-4c65-995c-bcadce524fb1" @@ -511,7 +511,7 @@ }, "input": { "payloads": [ - 1656707332859456300, + "1656707332859456300", { "ID": "temporal.fixture.completed.workflow.id", "RunID": "202dcff6-7f35-4c65-995c-bcadce524fb1" @@ -558,7 +558,7 @@ }, "input": { "payloads": [ - 1656707332859456300, + "1656707332859456300", { "ID": "temporal.fixture.completed.workflow.id", "RunID": "202dcff6-7f35-4c65-995c-bcadce524fb1" @@ -591,7 +591,7 @@ }, "input": { "payloads": [ - 1656707332859456300, + "1656707332859456300", { "ID": "temporal.fixture.completed.workflow.id", "RunID": "202dcff6-7f35-4c65-995c-bcadce524fb1" diff --git a/src/fixtures/events.children.json b/src/fixtures/events.children.json new file mode 100644 index 000000000..ca73b804b --- /dev/null +++ b/src/fixtures/events.children.json @@ -0,0 +1,2519 @@ +[ + { + "eventId": "1", + "eventTime": "2023-05-01T20:47:07.784152Z", + "eventType": "WorkflowExecutionStarted", + "version": "0", + "taskId": "2037127", + "workerMayIgnore": false, + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "workflow.sanity" + }, + "parentWorkflowNamespace": "", + "parentWorkflowNamespaceId": "", + "parentWorkflowExecution": null, + "parentInitiatedEventId": "0", + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "MTY4Mjk3NDAyNzc4MzE1ODAwMA==" + }, + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImNhbmFyeSI=" + } + ] + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "1320s", + "workflowTaskTimeout": "20s", + "continuedExecutionRunId": "", + "initiator": "Unspecified", + "continuedFailure": null, + "lastCompletionResult": null, + "originalExecutionRunId": "0f7fe2ea-4089-47c8-a53a-893bc0524cc2", + "identity": "35932@Alexs-MacBook-Pro.local@", + "firstExecutionRunId": "0f7fe2ea-4089-47c8-a53a-893bc0524cc2", + "retryPolicy": null, + "attempt": 1, + "workflowExecutionExpirationTime": null, + "cronSchedule": "", + "firstWorkflowTaskBackoff": "0s", + "memo": null, + "searchAttributes": null, + "prevAutoResetPoints": null, + "header": { + "fields": {} + }, + "parentInitiatedEventVersion": "0" + } + }, + { + "eventId": "2", + "eventTime": "2023-05-01T20:47:07.784195Z", + "eventType": "WorkflowTaskScheduled", + "version": "0", + "taskId": "2037128", + "workerMayIgnore": false, + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "startToCloseTimeout": "20s", + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": "2023-05-01T20:47:07.788344Z", + "eventType": "WorkflowTaskStarted", + "version": "0", + "taskId": "2037134", + "workerMayIgnore": false, + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "35932@Alexs-MacBook-Pro.local@", + "requestId": "f490d375-3e01-48c4-8214-2480ce4c5aaa", + "suggestContinueAsNew": false, + "historySizeBytes": "339" + } + }, + { + "eventId": "4", + "eventTime": "2023-05-01T20:47:07.790409Z", + "eventType": "WorkflowTaskCompleted", + "version": "0", + "taskId": "2037150", + "workerMayIgnore": false, + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "startedEventId": "3", + "identity": "35932@Alexs-MacBook-Pro.local@", + "binaryChecksum": "4c66ce02b6e876e584dcecdd9dbc34c0", + "workerVersioningId": null, + "sdkMetadata": null, + "meteringMetadata": null + } + }, + { + "eventId": "5", + "eventTime": "2023-05-01T20:47:07.790423Z", + "eventType": "MarkerRecorded", + "version": "0", + "taskId": "2037151", + "workerMayIgnore": false, + "markerRecordedEventAttributes": { + "markerName": "Version", + "details": { + "change-id": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImluaXRpYWwgdmVyc2lvbiI=" + } + ] + }, + "version": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "NA==" + } + ] + } + }, + "workflowTaskCompletedEventId": "4", + "header": null, + "failure": null + } + }, + { + "eventId": "6", + "eventTime": "2023-05-01T20:47:07.790581Z", + "eventType": "UpsertWorkflowSearchAttributes", + "version": "0", + "taskId": "2037152", + "workerMayIgnore": false, + "upsertWorkflowSearchAttributesEventAttributes": { + "workflowTaskCompletedEventId": "4", + "searchAttributes": { + "indexedFields": { + "TemporalChangeVersion": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZExpc3Q=" + }, + "data": "WyJpbml0aWFsIHZlcnNpb24tNCJd" + } + } + } + } + }, + { + "eventId": "7", + "eventTime": "2023-05-01T20:47:07.790585Z", + "eventType": "MarkerRecorded", + "version": "0", + "taskId": "2037153", + "workerMayIgnore": false, + "markerRecordedEventAttributes": { + "markerName": "SideEffect", + "details": { + "data": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "WyJ3b3JrZmxvdy5lY2hvIiwid29ya2Zsb3cuc2lnbmFsIiwid29ya2Zsb3cuc3RhbmRhcmQtdmlzaWJpbGl0eSIsIndvcmtmbG93LmFkdmFuY2VkLXZpc2liaWxpdHkiLCJ3b3JrZmxvdy5hZHZhbmNlZC12aXNpYmlsaXR5LnNjYW4iLCJ3b3JrZmxvdy5jb25jdXJyZW50LWV4ZWN1dGlvbiIsIndvcmtmbG93LnF1ZXJ5Iiwid29ya2Zsb3cudGltZW91dCIsIndvcmtmbG93LmxvY2FsYWN0aXZpdHkiLCJ3b3JrZmxvdy5jYW5jZWxsYXRpb24iLCJ3b3JrZmxvdy5yZXRyeS1hY3Rpdml0eSIsIndvcmtmbG93LnJldHJ5LXdvcmtmbG93Iiwid29ya2Zsb3cucmVzZXQiLCJ3b3JrZmxvdy5jb25uZWN0aXZpdHkiLCJ3b3JrZmxvdy5zY2hlZHVsZSJd" + } + ] + }, + "side-effect-id": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "MQ==" + } + ] + } + }, + "workflowTaskCompletedEventId": "4", + "header": null, + "failure": null + } + }, + { + "eventId": "8", + "eventTime": "2023-05-01T20:47:07.790765Z", + "eventType": "StartChildWorkflowExecutionInitiated", + "version": "0", + "taskId": "2037154", + "workerMayIgnore": false, + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.echo", + "workflowType": { + "name": "workflow.echo" + }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "MTY4Mjk3NDAyNzc4ODM0NDAwMA==" + }, + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImNhbmFyeSI=" + } + ] + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "1200s", + "workflowTaskTimeout": "20s", + "parentClosePolicy": "Terminate", + "control": "", + "workflowTaskCompletedEventId": "4", + "workflowIdReusePolicy": "AllowDuplicate", + "retryPolicy": null, + "cronSchedule": "", + "header": { + "fields": {} + }, + "memo": null, + "searchAttributes": { + "indexedFields": { + "CustomKeywordField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "ImNoaWxkV29ya2Zsb3dWYWx1ZSI=" + } + } + } + } + }, + { + "eventId": "9", + "eventTime": "2023-05-01T20:47:07.790825Z", + "eventType": "StartChildWorkflowExecutionInitiated", + "version": "0", + "taskId": "2037155", + "workerMayIgnore": false, + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.signal", + "workflowType": { + "name": "workflow.signal" + }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "MTY4Mjk3NDAyNzc4ODM0NDAwMA==" + }, + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImNhbmFyeSI=" + } + ] + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "1200s", + "workflowTaskTimeout": "20s", + "parentClosePolicy": "Terminate", + "control": "", + "workflowTaskCompletedEventId": "4", + "workflowIdReusePolicy": "AllowDuplicate", + "retryPolicy": null, + "cronSchedule": "", + "header": { + "fields": {} + }, + "memo": null, + "searchAttributes": { + "indexedFields": { + "CustomKeywordField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "ImNoaWxkV29ya2Zsb3dWYWx1ZSI=" + } + } + } + } + }, + { + "eventId": "10", + "eventTime": "2023-05-01T20:47:07.790878Z", + "eventType": "StartChildWorkflowExecutionInitiated", + "version": "0", + "taskId": "2037156", + "workerMayIgnore": false, + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.standard-visibility", + "workflowType": { + "name": "workflow.standard-visibility" + }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "MTY4Mjk3NDAyNzc4ODM0NDAwMA==" + }, + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImNhbmFyeSI=" + } + ] + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "1200s", + "workflowTaskTimeout": "20s", + "parentClosePolicy": "Terminate", + "control": "", + "workflowTaskCompletedEventId": "4", + "workflowIdReusePolicy": "AllowDuplicate", + "retryPolicy": null, + "cronSchedule": "", + "header": { + "fields": {} + }, + "memo": null, + "searchAttributes": { + "indexedFields": { + "CustomKeywordField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "ImNoaWxkV29ya2Zsb3dWYWx1ZSI=" + } + } + } + } + }, + { + "eventId": "11", + "eventTime": "2023-05-01T20:47:07.790935Z", + "eventType": "StartChildWorkflowExecutionInitiated", + "version": "0", + "taskId": "2037157", + "workerMayIgnore": false, + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.advanced-visibility", + "workflowType": { + "name": "workflow.advanced-visibility" + }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "MTY4Mjk3NDAyNzc4ODM0NDAwMA==" + }, + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImNhbmFyeSI=" + } + ] + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "1200s", + "workflowTaskTimeout": "20s", + "parentClosePolicy": "Terminate", + "control": "", + "workflowTaskCompletedEventId": "4", + "workflowIdReusePolicy": "AllowDuplicate", + "retryPolicy": null, + "cronSchedule": "", + "header": { + "fields": {} + }, + "memo": null, + "searchAttributes": { + "indexedFields": { + "CustomKeywordField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "ImNoaWxkV29ya2Zsb3dWYWx1ZSI=" + } + } + } + } + }, + { + "eventId": "12", + "eventTime": "2023-05-01T20:47:07.790984Z", + "eventType": "StartChildWorkflowExecutionInitiated", + "version": "0", + "taskId": "2037158", + "workerMayIgnore": false, + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.advanced-visibility.scan", + "workflowType": { + "name": "workflow.advanced-visibility.scan" + }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "MTY4Mjk3NDAyNzc4ODM0NDAwMA==" + }, + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImNhbmFyeSI=" + } + ] + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "1200s", + "workflowTaskTimeout": "20s", + "parentClosePolicy": "Terminate", + "control": "", + "workflowTaskCompletedEventId": "4", + "workflowIdReusePolicy": "AllowDuplicate", + "retryPolicy": null, + "cronSchedule": "", + "header": { + "fields": {} + }, + "memo": null, + "searchAttributes": { + "indexedFields": { + "CustomKeywordField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "ImNoaWxkV29ya2Zsb3dWYWx1ZSI=" + } + } + } + } + }, + { + "eventId": "13", + "eventTime": "2023-05-01T20:47:07.791034Z", + "eventType": "StartChildWorkflowExecutionInitiated", + "version": "0", + "taskId": "2037159", + "workerMayIgnore": false, + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.concurrent-execution", + "workflowType": { + "name": "workflow.concurrent-execution" + }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "MTY4Mjk3NDAyNzc4ODM0NDAwMA==" + }, + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImNhbmFyeSI=" + } + ] + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "1200s", + "workflowTaskTimeout": "20s", + "parentClosePolicy": "Terminate", + "control": "", + "workflowTaskCompletedEventId": "4", + "workflowIdReusePolicy": "AllowDuplicate", + "retryPolicy": null, + "cronSchedule": "", + "header": { + "fields": {} + }, + "memo": null, + "searchAttributes": { + "indexedFields": { + "CustomKeywordField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "ImNoaWxkV29ya2Zsb3dWYWx1ZSI=" + } + } + } + } + }, + { + "eventId": "14", + "eventTime": "2023-05-01T20:47:07.791082Z", + "eventType": "StartChildWorkflowExecutionInitiated", + "version": "0", + "taskId": "2037160", + "workerMayIgnore": false, + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.query", + "workflowType": { + "name": "workflow.query" + }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "MTY4Mjk3NDAyNzc4ODM0NDAwMA==" + }, + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImNhbmFyeSI=" + } + ] + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "1200s", + "workflowTaskTimeout": "20s", + "parentClosePolicy": "Terminate", + "control": "", + "workflowTaskCompletedEventId": "4", + "workflowIdReusePolicy": "AllowDuplicate", + "retryPolicy": null, + "cronSchedule": "", + "header": { + "fields": {} + }, + "memo": null, + "searchAttributes": { + "indexedFields": { + "CustomKeywordField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "ImNoaWxkV29ya2Zsb3dWYWx1ZSI=" + } + } + } + } + }, + { + "eventId": "15", + "eventTime": "2023-05-01T20:47:07.791134Z", + "eventType": "StartChildWorkflowExecutionInitiated", + "version": "0", + "taskId": "2037161", + "workerMayIgnore": false, + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.timeout", + "workflowType": { + "name": "workflow.timeout" + }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "MTY4Mjk3NDAyNzc4ODM0NDAwMA==" + }, + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImNhbmFyeSI=" + } + ] + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "1200s", + "workflowTaskTimeout": "20s", + "parentClosePolicy": "Terminate", + "control": "", + "workflowTaskCompletedEventId": "4", + "workflowIdReusePolicy": "AllowDuplicate", + "retryPolicy": null, + "cronSchedule": "", + "header": { + "fields": {} + }, + "memo": null, + "searchAttributes": { + "indexedFields": { + "CustomKeywordField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "ImNoaWxkV29ya2Zsb3dWYWx1ZSI=" + } + } + } + } + }, + { + "eventId": "16", + "eventTime": "2023-05-01T20:47:07.791214Z", + "eventType": "StartChildWorkflowExecutionInitiated", + "version": "0", + "taskId": "2037162", + "workerMayIgnore": false, + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.localactivity", + "workflowType": { + "name": "workflow.localactivity" + }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "MTY4Mjk3NDAyNzc4ODM0NDAwMA==" + }, + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImNhbmFyeSI=" + } + ] + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "1200s", + "workflowTaskTimeout": "20s", + "parentClosePolicy": "Terminate", + "control": "", + "workflowTaskCompletedEventId": "4", + "workflowIdReusePolicy": "AllowDuplicate", + "retryPolicy": null, + "cronSchedule": "", + "header": { + "fields": {} + }, + "memo": null, + "searchAttributes": { + "indexedFields": { + "CustomKeywordField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "ImNoaWxkV29ya2Zsb3dWYWx1ZSI=" + } + } + } + } + }, + { + "eventId": "17", + "eventTime": "2023-05-01T20:47:07.791265Z", + "eventType": "StartChildWorkflowExecutionInitiated", + "version": "0", + "taskId": "2037163", + "workerMayIgnore": false, + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.cancellation", + "workflowType": { + "name": "workflow.cancellation" + }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "MTY4Mjk3NDAyNzc4ODM0NDAwMA==" + }, + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImNhbmFyeSI=" + } + ] + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "1200s", + "workflowTaskTimeout": "20s", + "parentClosePolicy": "Terminate", + "control": "", + "workflowTaskCompletedEventId": "4", + "workflowIdReusePolicy": "AllowDuplicate", + "retryPolicy": null, + "cronSchedule": "", + "header": { + "fields": {} + }, + "memo": null, + "searchAttributes": { + "indexedFields": { + "CustomKeywordField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "ImNoaWxkV29ya2Zsb3dWYWx1ZSI=" + } + } + } + } + }, + { + "eventId": "18", + "eventTime": "2023-05-01T20:47:07.791312Z", + "eventType": "StartChildWorkflowExecutionInitiated", + "version": "0", + "taskId": "2037164", + "workerMayIgnore": false, + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.retry-activity", + "workflowType": { + "name": "workflow.retry-activity" + }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "MTY4Mjk3NDAyNzc4ODM0NDAwMA==" + }, + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImNhbmFyeSI=" + } + ] + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "1200s", + "workflowTaskTimeout": "20s", + "parentClosePolicy": "Terminate", + "control": "", + "workflowTaskCompletedEventId": "4", + "workflowIdReusePolicy": "AllowDuplicate", + "retryPolicy": null, + "cronSchedule": "", + "header": { + "fields": {} + }, + "memo": null, + "searchAttributes": { + "indexedFields": { + "CustomKeywordField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "ImNoaWxkV29ya2Zsb3dWYWx1ZSI=" + } + } + } + } + }, + { + "eventId": "19", + "eventTime": "2023-05-01T20:47:07.791359Z", + "eventType": "StartChildWorkflowExecutionInitiated", + "version": "0", + "taskId": "2037165", + "workerMayIgnore": false, + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.retry-workflow", + "workflowType": { + "name": "workflow.retry-workflow" + }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "MTY4Mjk3NDAyNzc4ODM0NDAwMA==" + }, + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImNhbmFyeSI=" + } + ] + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "1200s", + "workflowTaskTimeout": "20s", + "parentClosePolicy": "Terminate", + "control": "", + "workflowTaskCompletedEventId": "4", + "workflowIdReusePolicy": "AllowDuplicate", + "retryPolicy": null, + "cronSchedule": "", + "header": { + "fields": {} + }, + "memo": null, + "searchAttributes": { + "indexedFields": { + "CustomKeywordField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "ImNoaWxkV29ya2Zsb3dWYWx1ZSI=" + } + } + } + } + }, + { + "eventId": "20", + "eventTime": "2023-05-01T20:47:07.791438Z", + "eventType": "StartChildWorkflowExecutionInitiated", + "version": "0", + "taskId": "2037166", + "workerMayIgnore": false, + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.reset", + "workflowType": { + "name": "workflow.reset" + }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "MTY4Mjk3NDAyNzc4ODM0NDAwMA==" + }, + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImNhbmFyeSI=" + } + ] + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "1200s", + "workflowTaskTimeout": "20s", + "parentClosePolicy": "Terminate", + "control": "", + "workflowTaskCompletedEventId": "4", + "workflowIdReusePolicy": "AllowDuplicate", + "retryPolicy": null, + "cronSchedule": "", + "header": { + "fields": {} + }, + "memo": null, + "searchAttributes": { + "indexedFields": { + "CustomKeywordField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "ImNoaWxkV29ya2Zsb3dWYWx1ZSI=" + } + } + } + } + }, + { + "eventId": "21", + "eventTime": "2023-05-01T20:47:07.791504Z", + "eventType": "StartChildWorkflowExecutionInitiated", + "version": "0", + "taskId": "2037167", + "workerMayIgnore": false, + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.connectivity", + "workflowType": { + "name": "workflow.connectivity" + }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "MTY4Mjk3NDAyNzc4ODM0NDAwMA==" + }, + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImNhbmFyeSI=" + } + ] + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "1200s", + "workflowTaskTimeout": "20s", + "parentClosePolicy": "Terminate", + "control": "", + "workflowTaskCompletedEventId": "4", + "workflowIdReusePolicy": "AllowDuplicate", + "retryPolicy": null, + "cronSchedule": "", + "header": { + "fields": {} + }, + "memo": null, + "searchAttributes": { + "indexedFields": { + "CustomKeywordField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "ImNoaWxkV29ya2Zsb3dWYWx1ZSI=" + } + } + } + } + }, + { + "eventId": "22", + "eventTime": "2023-05-01T20:47:07.791551Z", + "eventType": "StartChildWorkflowExecutionInitiated", + "version": "0", + "taskId": "2037168", + "workerMayIgnore": false, + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.schedule", + "workflowType": { + "name": "workflow.schedule" + }, + "taskQueue": { + "name": "canary-task-queue", + "kind": "Normal" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "MTY4Mjk3NDAyNzc4ODM0NDAwMA==" + }, + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImNhbmFyeSI=" + } + ] + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "1200s", + "workflowTaskTimeout": "20s", + "parentClosePolicy": "Terminate", + "control": "", + "workflowTaskCompletedEventId": "4", + "workflowIdReusePolicy": "AllowDuplicate", + "retryPolicy": null, + "cronSchedule": "", + "header": { + "fields": {} + }, + "memo": null, + "searchAttributes": { + "indexedFields": { + "CustomKeywordField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "ImNoaWxkV29ya2Zsb3dWYWx1ZSI=" + } + } + } + } + }, + { + "eventId": "23", + "eventTime": "2023-05-01T20:47:07.793294Z", + "eventType": "ChildWorkflowExecutionStarted", + "version": "0", + "taskId": "2037191", + "workerMayIgnore": false, + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "initiatedEventId": "22", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.schedule", + "runId": "3f169b97-7f96-4ba9-9262-ac14170334db" + }, + "workflowType": { + "name": "workflow.schedule" + }, + "header": { + "fields": {} + } + } + }, + { + "eventId": "24", + "eventTime": "2023-05-01T20:47:07.793301Z", + "eventType": "WorkflowTaskScheduled", + "version": "0", + "taskId": "2037192", + "workerMayIgnore": false, + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Alexs-MacBook-Pro.local:6b7f8929-6e5e-44e0-ab50-6a5486c49168", + "kind": "Sticky" + }, + "startToCloseTimeout": "20s", + "attempt": 1 + } + }, + { + "eventId": "25", + "eventTime": "2023-05-01T20:47:07.795126Z", + "eventType": "ChildWorkflowExecutionStarted", + "version": "0", + "taskId": "2037205", + "workerMayIgnore": false, + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "initiatedEventId": "14", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.query", + "runId": "5ec35484-2698-4e06-a998-e8b808633eb4" + }, + "workflowType": { + "name": "workflow.query" + }, + "header": { + "fields": {} + } + } + }, + { + "eventId": "26", + "eventTime": "2023-05-01T20:47:07.797032Z", + "eventType": "ChildWorkflowExecutionStarted", + "version": "0", + "taskId": "2037224", + "workerMayIgnore": false, + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "initiatedEventId": "8", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.echo", + "runId": "00b515ce-a010-4864-bb33-d04deb228ace" + }, + "workflowType": { + "name": "workflow.echo" + }, + "header": { + "fields": {} + } + } + }, + { + "eventId": "27", + "eventTime": "2023-05-01T20:47:07.800310Z", + "eventType": "ChildWorkflowExecutionStarted", + "version": "0", + "taskId": "2037243", + "workerMayIgnore": false, + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "initiatedEventId": "9", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.signal", + "runId": "23a01faf-8a86-4acb-940a-a503eb9e14c3" + }, + "workflowType": { + "name": "workflow.signal" + }, + "header": { + "fields": {} + } + } + }, + { + "eventId": "28", + "eventTime": "2023-05-01T20:47:07.806608Z", + "eventType": "ChildWorkflowExecutionStarted", + "version": "0", + "taskId": "2037273", + "workerMayIgnore": false, + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "initiatedEventId": "18", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.retry-activity", + "runId": "6398e686-2e21-4758-9c5c-09600ced52b1" + }, + "workflowType": { + "name": "workflow.retry-activity" + }, + "header": { + "fields": {} + } + } + }, + { + "eventId": "29", + "eventTime": "2023-05-01T20:47:07.811553Z", + "eventType": "ChildWorkflowExecutionStarted", + "version": "0", + "taskId": "2037297", + "workerMayIgnore": false, + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "initiatedEventId": "10", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.standard-visibility", + "runId": "16c2932f-60fb-48c2-908a-123de68dc3b3" + }, + "workflowType": { + "name": "workflow.standard-visibility" + }, + "header": { + "fields": {} + } + } + }, + { + "eventId": "30", + "eventTime": "2023-05-01T20:47:07.815572Z", + "eventType": "ChildWorkflowExecutionStarted", + "version": "0", + "taskId": "2037344", + "workerMayIgnore": false, + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "initiatedEventId": "15", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.timeout", + "runId": "b9c4a071-40ef-4e92-9dd2-69c331a90de4" + }, + "workflowType": { + "name": "workflow.timeout" + }, + "header": { + "fields": {} + } + } + }, + { + "eventId": "31", + "eventTime": "2023-05-01T20:47:07.822029Z", + "eventType": "ChildWorkflowExecutionStarted", + "version": "0", + "taskId": "2037399", + "workerMayIgnore": false, + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "initiatedEventId": "11", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.advanced-visibility", + "runId": "d13c6438-be5d-4511-ad5d-cea4585bbab8" + }, + "workflowType": { + "name": "workflow.advanced-visibility" + }, + "header": { + "fields": {} + } + } + }, + { + "eventId": "32", + "eventTime": "2023-05-01T20:47:07.829350Z", + "eventType": "ChildWorkflowExecutionStarted", + "version": "0", + "taskId": "2037443", + "workerMayIgnore": false, + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "initiatedEventId": "16", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.localactivity", + "runId": "ec8921e6-192b-4e3e-b843-f04109fbc4dd" + }, + "workflowType": { + "name": "workflow.localactivity" + }, + "header": { + "fields": {} + } + } + }, + { + "eventId": "33", + "eventTime": "2023-05-01T20:47:07.833056Z", + "eventType": "ChildWorkflowExecutionStarted", + "version": "0", + "taskId": "2037460", + "workerMayIgnore": false, + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "initiatedEventId": "12", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.advanced-visibility.scan", + "runId": "12dd04c9-ae7f-4749-ae11-d410311062e0" + }, + "workflowType": { + "name": "workflow.advanced-visibility.scan" + }, + "header": { + "fields": {} + } + } + }, + { + "eventId": "34", + "eventTime": "2023-05-01T20:47:07.834195Z", + "eventType": "ChildWorkflowExecutionStarted", + "version": "0", + "taskId": "2037468", + "workerMayIgnore": false, + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "initiatedEventId": "17", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.cancellation", + "runId": "875e87a0-677e-47d9-9854-17d6d67c5247" + }, + "workflowType": { + "name": "workflow.cancellation" + }, + "header": { + "fields": {} + } + } + }, + { + "eventId": "35", + "eventTime": "2023-05-01T20:47:07.834843Z", + "eventType": "ChildWorkflowExecutionStarted", + "version": "0", + "taskId": "2037479", + "workerMayIgnore": false, + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "initiatedEventId": "13", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.concurrent-execution", + "runId": "ba57ac41-ac33-473a-a01f-6ce7bd533da2" + }, + "workflowType": { + "name": "workflow.concurrent-execution" + }, + "header": { + "fields": {} + } + } + }, + { + "eventId": "36", + "eventTime": "2023-05-01T20:47:07.835574Z", + "eventType": "ChildWorkflowExecutionStarted", + "version": "0", + "taskId": "2037490", + "workerMayIgnore": false, + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "initiatedEventId": "19", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.retry-workflow", + "runId": "0b12815b-0dce-477a-96eb-dfdddac1476c" + }, + "workflowType": { + "name": "workflow.retry-workflow" + }, + "header": { + "fields": {} + } + } + }, + { + "eventId": "37", + "eventTime": "2023-05-01T20:47:07.836529Z", + "eventType": "ChildWorkflowExecutionStarted", + "version": "0", + "taskId": "2037501", + "workerMayIgnore": false, + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "initiatedEventId": "20", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.reset", + "runId": "7b03c173-51b5-4317-84cf-2101b9c89547" + }, + "workflowType": { + "name": "workflow.reset" + }, + "header": { + "fields": {} + } + } + }, + { + "eventId": "38", + "eventTime": "2023-05-01T20:47:07.837312Z", + "eventType": "ChildWorkflowExecutionStarted", + "version": "0", + "taskId": "2037512", + "workerMayIgnore": false, + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "initiatedEventId": "21", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.connectivity", + "runId": "6184621f-3f9f-4bbc-82b3-d6583d9921a3" + }, + "workflowType": { + "name": "workflow.connectivity" + }, + "header": { + "fields": {} + } + } + }, + { + "eventId": "39", + "eventTime": "2023-05-01T20:47:07.837837Z", + "eventType": "WorkflowTaskStarted", + "version": "0", + "taskId": "2037518", + "workerMayIgnore": false, + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "24", + "identity": "35932@Alexs-MacBook-Pro.local@", + "requestId": "44a84fcf-f1c6-46f0-bddb-6d691b61e23f", + "suggestContinueAsNew": false, + "historySizeBytes": "10509" + } + }, + { + "eventId": "40", + "eventTime": "2023-05-01T20:47:07.839625Z", + "eventType": "WorkflowTaskCompleted", + "version": "0", + "taskId": "2037525", + "workerMayIgnore": false, + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "24", + "startedEventId": "39", + "identity": "35932@Alexs-MacBook-Pro.local@", + "binaryChecksum": "4c66ce02b6e876e584dcecdd9dbc34c0", + "workerVersioningId": null, + "sdkMetadata": null, + "meteringMetadata": null + } + }, + { + "eventId": "41", + "eventTime": "2023-05-01T20:47:07.867397Z", + "eventType": "ChildWorkflowExecutionCompleted", + "version": "0", + "taskId": "2037527", + "workerMayIgnore": false, + "childWorkflowExecutionCompletedEventAttributes": { + "result": null, + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.echo", + "runId": "00b515ce-a010-4864-bb33-d04deb228ace" + }, + "workflowType": { + "name": "workflow.echo" + }, + "initiatedEventId": "8", + "startedEventId": "26" + } + }, + { + "eventId": "42", + "eventTime": "2023-05-01T20:47:07.867400Z", + "eventType": "WorkflowTaskScheduled", + "version": "0", + "taskId": "2037528", + "workerMayIgnore": false, + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Alexs-MacBook-Pro.local:6b7f8929-6e5e-44e0-ab50-6a5486c49168", + "kind": "Sticky" + }, + "startToCloseTimeout": "20s", + "attempt": 1 + } + }, + { + "eventId": "43", + "eventTime": "2023-05-01T20:47:07.918072Z", + "eventType": "WorkflowTaskStarted", + "version": "0", + "taskId": "2037720", + "workerMayIgnore": false, + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "42", + "identity": "35932@Alexs-MacBook-Pro.local@", + "requestId": "d715e600-8757-4070-97f4-6b7640a28a7b", + "suggestContinueAsNew": false, + "historySizeBytes": "11018" + } + }, + { + "eventId": "44", + "eventTime": "2023-05-01T20:47:07.922396Z", + "eventType": "WorkflowTaskCompleted", + "version": "0", + "taskId": "2037743", + "workerMayIgnore": false, + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "42", + "startedEventId": "43", + "identity": "35932@Alexs-MacBook-Pro.local@", + "binaryChecksum": "4c66ce02b6e876e584dcecdd9dbc34c0", + "workerVersioningId": null, + "sdkMetadata": null, + "meteringMetadata": null + } + }, + { + "eventId": "45", + "eventTime": "2023-05-01T20:47:07.967762Z", + "eventType": "ChildWorkflowExecutionCompleted", + "version": "0", + "taskId": "2037894", + "workerMayIgnore": false, + "childWorkflowExecutionCompletedEventAttributes": { + "result": null, + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.query", + "runId": "5ec35484-2698-4e06-a998-e8b808633eb4" + }, + "workflowType": { + "name": "workflow.query" + }, + "initiatedEventId": "14", + "startedEventId": "25" + } + }, + { + "eventId": "46", + "eventTime": "2023-05-01T20:47:07.967765Z", + "eventType": "WorkflowTaskScheduled", + "version": "0", + "taskId": "2037895", + "workerMayIgnore": false, + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Alexs-MacBook-Pro.local:6b7f8929-6e5e-44e0-ab50-6a5486c49168", + "kind": "Sticky" + }, + "startToCloseTimeout": "20s", + "attempt": 1 + } + }, + { + "eventId": "47", + "eventTime": "2023-05-01T20:47:08.018053Z", + "eventType": "WorkflowTaskStarted", + "version": "0", + "taskId": "2038070", + "workerMayIgnore": false, + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "46", + "identity": "35932@Alexs-MacBook-Pro.local@", + "requestId": "f38f2f55-037c-4534-9b60-90c1c91cc62e", + "suggestContinueAsNew": false, + "historySizeBytes": "11529" + } + }, + { + "eventId": "48", + "eventTime": "2023-05-01T20:47:08.020225Z", + "eventType": "WorkflowTaskCompleted", + "version": "0", + "taskId": "2038091", + "workerMayIgnore": false, + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "46", + "startedEventId": "47", + "identity": "35932@Alexs-MacBook-Pro.local@", + "binaryChecksum": "4c66ce02b6e876e584dcecdd9dbc34c0", + "workerVersioningId": null, + "sdkMetadata": null, + "meteringMetadata": null + } + }, + { + "eventId": "49", + "eventTime": "2023-05-01T20:47:08.018658Z", + "eventType": "ChildWorkflowExecutionCompleted", + "version": "0", + "taskId": "2038092", + "workerMayIgnore": false, + "childWorkflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImRhdGElMiA9PSAwIg==" + } + ] + }, + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.localactivity", + "runId": "ec8921e6-192b-4e3e-b843-f04109fbc4dd" + }, + "workflowType": { + "name": "workflow.localactivity" + }, + "initiatedEventId": "16", + "startedEventId": "32" + } + }, + { + "eventId": "50", + "eventTime": "2023-05-01T20:47:08.020231Z", + "eventType": "WorkflowTaskScheduled", + "version": "0", + "taskId": "2038093", + "workerMayIgnore": false, + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Alexs-MacBook-Pro.local:6b7f8929-6e5e-44e0-ab50-6a5486c49168", + "kind": "Sticky" + }, + "startToCloseTimeout": "20s", + "attempt": 1 + } + }, + { + "eventId": "51", + "eventTime": "2023-05-01T20:47:08.020233Z", + "eventType": "WorkflowTaskStarted", + "version": "0", + "taskId": "2038094", + "workerMayIgnore": false, + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "50", + "identity": "35932@Alexs-MacBook-Pro.local@", + "requestId": "request-from-RespondWorkflowTaskCompleted", + "suggestContinueAsNew": false, + "historySizeBytes": "11629" + } + }, + { + "eventId": "52", + "eventTime": "2023-05-01T20:47:08.024364Z", + "eventType": "WorkflowTaskCompleted", + "version": "0", + "taskId": "2038129", + "workerMayIgnore": false, + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "50", + "startedEventId": "51", + "identity": "35932@Alexs-MacBook-Pro.local@", + "binaryChecksum": "4c66ce02b6e876e584dcecdd9dbc34c0", + "workerVersioningId": null, + "sdkMetadata": null, + "meteringMetadata": null + } + }, + { + "eventId": "53", + "eventTime": "2023-05-01T20:47:08.117819Z", + "eventType": "ChildWorkflowExecutionCompleted", + "version": "0", + "taskId": "2038346", + "workerMayIgnore": false, + "childWorkflowExecutionCompletedEventAttributes": { + "result": null, + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.connectivity", + "runId": "6184621f-3f9f-4bbc-82b3-d6583d9921a3" + }, + "workflowType": { + "name": "workflow.connectivity" + }, + "initiatedEventId": "21", + "startedEventId": "38" + } + }, + { + "eventId": "54", + "eventTime": "2023-05-01T20:47:08.117822Z", + "eventType": "WorkflowTaskScheduled", + "version": "0", + "taskId": "2038347", + "workerMayIgnore": false, + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Alexs-MacBook-Pro.local:6b7f8929-6e5e-44e0-ab50-6a5486c49168", + "kind": "Sticky" + }, + "startToCloseTimeout": "20s", + "attempt": 1 + } + }, + { + "eventId": "55", + "eventTime": "2023-05-01T20:47:08.168262Z", + "eventType": "WorkflowTaskStarted", + "version": "0", + "taskId": "2038524", + "workerMayIgnore": false, + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "54", + "identity": "35932@Alexs-MacBook-Pro.local@", + "requestId": "f9b19cd8-ba89-42cd-9419-c02a76915a0c", + "suggestContinueAsNew": false, + "historySizeBytes": "12621" + } + }, + { + "eventId": "56", + "eventTime": "2023-05-01T20:47:08.171373Z", + "eventType": "WorkflowTaskCompleted", + "version": "0", + "taskId": "2038540", + "workerMayIgnore": false, + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "54", + "startedEventId": "55", + "identity": "35932@Alexs-MacBook-Pro.local@", + "binaryChecksum": "4c66ce02b6e876e584dcecdd9dbc34c0", + "workerVersioningId": null, + "sdkMetadata": null, + "meteringMetadata": null + } + }, + { + "eventId": "57", + "eventTime": "2023-05-01T20:47:08.317723Z", + "eventType": "ChildWorkflowExecutionCompleted", + "version": "0", + "taskId": "2038765", + "workerMayIgnore": false, + "childWorkflowExecutionCompletedEventAttributes": { + "result": null, + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.concurrent-execution", + "runId": "ba57ac41-ac33-473a-a01f-6ce7bd533da2" + }, + "workflowType": { + "name": "workflow.concurrent-execution" + }, + "initiatedEventId": "13", + "startedEventId": "35" + } + }, + { + "eventId": "58", + "eventTime": "2023-05-01T20:47:08.317726Z", + "eventType": "WorkflowTaskScheduled", + "version": "0", + "taskId": "2038766", + "workerMayIgnore": false, + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Alexs-MacBook-Pro.local:6b7f8929-6e5e-44e0-ab50-6a5486c49168", + "kind": "Sticky" + }, + "startToCloseTimeout": "20s", + "attempt": 1 + } + }, + { + "eventId": "59", + "eventTime": "2023-05-01T20:47:08.368209Z", + "eventType": "WorkflowTaskStarted", + "version": "0", + "taskId": "2038772", + "workerMayIgnore": false, + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "58", + "identity": "35932@Alexs-MacBook-Pro.local@", + "requestId": "4f9c6400-8896-4ecd-aca6-5827072f989b", + "suggestContinueAsNew": false, + "historySizeBytes": "13161" + } + }, + { + "eventId": "60", + "eventTime": "2023-05-01T20:47:08.372875Z", + "eventType": "WorkflowTaskCompleted", + "version": "0", + "taskId": "2038780", + "workerMayIgnore": false, + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "58", + "startedEventId": "59", + "identity": "35932@Alexs-MacBook-Pro.local@", + "binaryChecksum": "4c66ce02b6e876e584dcecdd9dbc34c0", + "workerVersioningId": null, + "sdkMetadata": null, + "meteringMetadata": null + } + }, + { + "eventId": "61", + "eventTime": "2023-05-01T20:47:08.718064Z", + "eventType": "ChildWorkflowExecutionCompleted", + "version": "0", + "taskId": "2038858", + "workerMayIgnore": false, + "childWorkflowExecutionCompletedEventAttributes": { + "result": null, + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.signal", + "runId": "23a01faf-8a86-4acb-940a-a503eb9e14c3" + }, + "workflowType": { + "name": "workflow.signal" + }, + "initiatedEventId": "9", + "startedEventId": "27" + } + }, + { + "eventId": "62", + "eventTime": "2023-05-01T20:47:08.718082Z", + "eventType": "WorkflowTaskScheduled", + "version": "0", + "taskId": "2038859", + "workerMayIgnore": false, + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Alexs-MacBook-Pro.local:6b7f8929-6e5e-44e0-ab50-6a5486c49168", + "kind": "Sticky" + }, + "startToCloseTimeout": "20s", + "attempt": 1 + } + }, + { + "eventId": "63", + "eventTime": "2023-05-01T20:47:08.770129Z", + "eventType": "WorkflowTaskStarted", + "version": "0", + "taskId": "2038863", + "workerMayIgnore": false, + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "62", + "identity": "35932@Alexs-MacBook-Pro.local@", + "requestId": "ed71e4ba-c5bd-42f3-bff6-1f6d0c9c5949", + "suggestContinueAsNew": false, + "historySizeBytes": "13674" + } + }, + { + "eventId": "64", + "eventTime": "2023-05-01T20:47:08.782945Z", + "eventType": "WorkflowTaskCompleted", + "version": "0", + "taskId": "2038867", + "workerMayIgnore": false, + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "62", + "startedEventId": "63", + "identity": "35932@Alexs-MacBook-Pro.local@", + "binaryChecksum": "4c66ce02b6e876e584dcecdd9dbc34c0", + "workerVersioningId": null, + "sdkMetadata": null, + "meteringMetadata": null + } + }, + { + "eventId": "65", + "eventTime": "2023-05-01T20:47:08.918440Z", + "eventType": "ChildWorkflowExecutionCompleted", + "version": "0", + "taskId": "2038885", + "workerMayIgnore": false, + "childWorkflowExecutionCompletedEventAttributes": { + "result": null, + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.timeout", + "runId": "b9c4a071-40ef-4e92-9dd2-69c331a90de4" + }, + "workflowType": { + "name": "workflow.timeout" + }, + "initiatedEventId": "15", + "startedEventId": "30" + } + }, + { + "eventId": "66", + "eventTime": "2023-05-01T20:47:08.918450Z", + "eventType": "WorkflowTaskScheduled", + "version": "0", + "taskId": "2038886", + "workerMayIgnore": false, + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Alexs-MacBook-Pro.local:6b7f8929-6e5e-44e0-ab50-6a5486c49168", + "kind": "Sticky" + }, + "startToCloseTimeout": "20s", + "attempt": 1 + } + }, + { + "eventId": "67", + "eventTime": "2023-05-01T20:47:08.967806Z", + "eventType": "WorkflowTaskStarted", + "version": "0", + "taskId": "2038890", + "workerMayIgnore": false, + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "66", + "identity": "35932@Alexs-MacBook-Pro.local@", + "requestId": "5c8393db-f765-48e9-ae5b-74b87e14c34f", + "suggestContinueAsNew": false, + "historySizeBytes": "14189" + } + }, + { + "eventId": "68", + "eventTime": "2023-05-01T20:47:08.971090Z", + "eventType": "WorkflowTaskCompleted", + "version": "0", + "taskId": "2038894", + "workerMayIgnore": false, + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "66", + "startedEventId": "67", + "identity": "35932@Alexs-MacBook-Pro.local@", + "binaryChecksum": "4c66ce02b6e876e584dcecdd9dbc34c0", + "workerVersioningId": null, + "sdkMetadata": null, + "meteringMetadata": null + } + }, + { + "eventId": "69", + "eventTime": "2023-05-01T20:47:10.268997Z", + "eventType": "ChildWorkflowExecutionCompleted", + "version": "0", + "taskId": "2039387", + "workerMayIgnore": false, + "childWorkflowExecutionCompletedEventAttributes": { + "result": null, + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.retry-workflow", + "runId": "0b12815b-0dce-477a-96eb-dfdddac1476c" + }, + "workflowType": { + "name": "workflow.retry-workflow" + }, + "initiatedEventId": "19", + "startedEventId": "36" + } + }, + { + "eventId": "70", + "eventTime": "2023-05-01T20:47:10.269002Z", + "eventType": "WorkflowTaskScheduled", + "version": "0", + "taskId": "2039388", + "workerMayIgnore": false, + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Alexs-MacBook-Pro.local:6b7f8929-6e5e-44e0-ab50-6a5486c49168", + "kind": "Sticky" + }, + "startToCloseTimeout": "20s", + "attempt": 1 + } + }, + { + "eventId": "71", + "eventTime": "2023-05-01T20:47:10.319378Z", + "eventType": "WorkflowTaskStarted", + "version": "0", + "taskId": "2039420", + "workerMayIgnore": false, + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "70", + "identity": "35932@Alexs-MacBook-Pro.local@", + "requestId": "c8f42118-bac8-4e8c-8d7c-25ea39e80b37", + "suggestContinueAsNew": false, + "historySizeBytes": "14718" + } + }, + { + "eventId": "72", + "eventTime": "2023-05-01T20:47:10.336506Z", + "eventType": "WorkflowTaskCompleted", + "version": "0", + "taskId": "2039442", + "workerMayIgnore": false, + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "70", + "startedEventId": "71", + "identity": "35932@Alexs-MacBook-Pro.local@", + "binaryChecksum": "4c66ce02b6e876e584dcecdd9dbc34c0", + "workerVersioningId": null, + "sdkMetadata": null, + "meteringMetadata": null + } + }, + { + "eventId": "73", + "eventTime": "2023-05-01T20:47:10.570045Z", + "eventType": "ChildWorkflowExecutionCompleted", + "version": "0", + "taskId": "2039541", + "workerMayIgnore": false, + "childWorkflowExecutionCompletedEventAttributes": { + "result": null, + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.reset", + "runId": "7b03c173-51b5-4317-84cf-2101b9c89547" + }, + "workflowType": { + "name": "workflow.reset" + }, + "initiatedEventId": "20", + "startedEventId": "37" + } + }, + { + "eventId": "74", + "eventTime": "2023-05-01T20:47:10.570059Z", + "eventType": "WorkflowTaskScheduled", + "version": "0", + "taskId": "2039542", + "workerMayIgnore": false, + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Alexs-MacBook-Pro.local:6b7f8929-6e5e-44e0-ab50-6a5486c49168", + "kind": "Sticky" + }, + "startToCloseTimeout": "20s", + "attempt": 1 + } + }, + { + "eventId": "75", + "eventTime": "2023-05-01T20:47:10.583841Z", + "eventType": "ChildWorkflowExecutionCompleted", + "version": "0", + "taskId": "2039546", + "workerMayIgnore": false, + "childWorkflowExecutionCompletedEventAttributes": { + "result": null, + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.cancellation", + "runId": "875e87a0-677e-47d9-9854-17d6d67c5247" + }, + "workflowType": { + "name": "workflow.cancellation" + }, + "initiatedEventId": "17", + "startedEventId": "34" + } + }, + { + "eventId": "76", + "eventTime": "2023-05-01T20:47:10.620945Z", + "eventType": "WorkflowTaskStarted", + "version": "0", + "taskId": "2039548", + "workerMayIgnore": false, + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "74", + "identity": "35932@Alexs-MacBook-Pro.local@", + "requestId": "d07b0abd-30ac-4491-94ad-6b971bf3e799", + "suggestContinueAsNew": false, + "historySizeBytes": "15459" + } + }, + { + "eventId": "77", + "eventTime": "2023-05-01T20:47:10.632230Z", + "eventType": "WorkflowTaskCompleted", + "version": "0", + "taskId": "2039552", + "workerMayIgnore": false, + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "74", + "startedEventId": "76", + "identity": "35932@Alexs-MacBook-Pro.local@", + "binaryChecksum": "4c66ce02b6e876e584dcecdd9dbc34c0", + "workerVersioningId": null, + "sdkMetadata": null, + "meteringMetadata": null + } + }, + { + "eventId": "78", + "eventTime": "2023-05-01T20:47:11.855745Z", + "eventType": "ChildWorkflowExecutionCompleted", + "version": "0", + "taskId": "2039588", + "workerMayIgnore": false, + "childWorkflowExecutionCompletedEventAttributes": { + "result": null, + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.standard-visibility", + "runId": "16c2932f-60fb-48c2-908a-123de68dc3b3" + }, + "workflowType": { + "name": "workflow.standard-visibility" + }, + "initiatedEventId": "10", + "startedEventId": "29" + } + }, + { + "eventId": "79", + "eventTime": "2023-05-01T20:47:11.855748Z", + "eventType": "WorkflowTaskScheduled", + "version": "0", + "taskId": "2039589", + "workerMayIgnore": false, + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Alexs-MacBook-Pro.local:6b7f8929-6e5e-44e0-ab50-6a5486c49168", + "kind": "Sticky" + }, + "startToCloseTimeout": "20s", + "attempt": 1 + } + }, + { + "eventId": "80", + "eventTime": "2023-05-01T20:47:11.857254Z", + "eventType": "WorkflowTaskStarted", + "version": "0", + "taskId": "2039593", + "workerMayIgnore": false, + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "79", + "identity": "35932@Alexs-MacBook-Pro.local@", + "requestId": "2506a30d-1006-4582-886a-68b413ad8b23", + "suggestContinueAsNew": false, + "historySizeBytes": "15999" + } + }, + { + "eventId": "81", + "eventTime": "2023-05-01T20:47:11.858827Z", + "eventType": "WorkflowTaskCompleted", + "version": "0", + "taskId": "2039597", + "workerMayIgnore": false, + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "79", + "startedEventId": "80", + "identity": "35932@Alexs-MacBook-Pro.local@", + "binaryChecksum": "4c66ce02b6e876e584dcecdd9dbc34c0", + "workerVersioningId": null, + "sdkMetadata": null, + "meteringMetadata": null + } + }, + { + "eventId": "82", + "eventTime": "2023-05-01T20:47:11.883929Z", + "eventType": "ChildWorkflowExecutionCompleted", + "version": "0", + "taskId": "2039647", + "workerMayIgnore": false, + "childWorkflowExecutionCompletedEventAttributes": { + "result": null, + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.advanced-visibility", + "runId": "d13c6438-be5d-4511-ad5d-cea4585bbab8" + }, + "workflowType": { + "name": "workflow.advanced-visibility" + }, + "initiatedEventId": "11", + "startedEventId": "31" + } + }, + { + "eventId": "83", + "eventTime": "2023-05-01T20:47:11.883936Z", + "eventType": "WorkflowTaskScheduled", + "version": "0", + "taskId": "2039648", + "workerMayIgnore": false, + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Alexs-MacBook-Pro.local:6b7f8929-6e5e-44e0-ab50-6a5486c49168", + "kind": "Sticky" + }, + "startToCloseTimeout": "20s", + "attempt": 1 + } + }, + { + "eventId": "84", + "eventTime": "2023-05-01T20:47:11.885515Z", + "eventType": "WorkflowTaskStarted", + "version": "0", + "taskId": "2039652", + "workerMayIgnore": false, + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "83", + "identity": "35932@Alexs-MacBook-Pro.local@", + "requestId": "118a69e2-ef7e-48e0-90cf-4c784f7a0c5b", + "suggestContinueAsNew": false, + "historySizeBytes": "16539" + } + }, + { + "eventId": "85", + "eventTime": "2023-05-01T20:47:11.887280Z", + "eventType": "WorkflowTaskCompleted", + "version": "0", + "taskId": "2039658", + "workerMayIgnore": false, + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "83", + "startedEventId": "84", + "identity": "35932@Alexs-MacBook-Pro.local@", + "binaryChecksum": "4c66ce02b6e876e584dcecdd9dbc34c0", + "workerVersioningId": null, + "sdkMetadata": null, + "meteringMetadata": null + } + }, + { + "eventId": "86", + "eventTime": "2023-05-01T20:47:19.280327Z", + "eventType": "ChildWorkflowExecutionCompleted", + "version": "0", + "taskId": "2039963", + "workerMayIgnore": false, + "childWorkflowExecutionCompletedEventAttributes": { + "result": null, + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.schedule", + "runId": "3f169b97-7f96-4ba9-9262-ac14170334db" + }, + "workflowType": { + "name": "workflow.schedule" + }, + "initiatedEventId": "22", + "startedEventId": "23" + } + }, + { + "eventId": "87", + "eventTime": "2023-05-01T20:47:19.280331Z", + "eventType": "WorkflowTaskScheduled", + "version": "0", + "taskId": "2039964", + "workerMayIgnore": false, + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Alexs-MacBook-Pro.local:6b7f8929-6e5e-44e0-ab50-6a5486c49168", + "kind": "Sticky" + }, + "startToCloseTimeout": "20s", + "attempt": 1 + } + }, + { + "eventId": "88", + "eventTime": "2023-05-01T20:47:19.282311Z", + "eventType": "WorkflowTaskStarted", + "version": "0", + "taskId": "2039968", + "workerMayIgnore": false, + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "87", + "identity": "35932@Alexs-MacBook-Pro.local@", + "requestId": "bbf68f5d-ba58-4e05-aa2b-37b1f559b8aa", + "suggestContinueAsNew": false, + "historySizeBytes": "17057" + } + }, + { + "eventId": "89", + "eventTime": "2023-05-01T20:47:19.283871Z", + "eventType": "WorkflowTaskCompleted", + "version": "0", + "taskId": "2039972", + "workerMayIgnore": false, + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "87", + "startedEventId": "88", + "identity": "35932@Alexs-MacBook-Pro.local@", + "binaryChecksum": "4c66ce02b6e876e584dcecdd9dbc34c0", + "workerVersioningId": null, + "sdkMetadata": null, + "meteringMetadata": null + } + }, + { + "eventId": "90", + "eventTime": "2023-05-01T20:47:45.878719Z", + "eventType": "ChildWorkflowExecutionCanceled", + "version": "0", + "taskId": "2042797", + "workerMayIgnore": false, + "childWorkflowExecutionCanceledEventAttributes": { + "details": null, + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.advanced-visibility.scan", + "runId": "12dd04c9-ae7f-4749-ae11-d410311062e0" + }, + "workflowType": { + "name": "workflow.advanced-visibility.scan" + }, + "initiatedEventId": "12", + "startedEventId": "33" + } + }, + { + "eventId": "91", + "eventTime": "2023-05-01T20:47:45.878725Z", + "eventType": "WorkflowTaskScheduled", + "version": "0", + "taskId": "2042798", + "workerMayIgnore": false, + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Alexs-MacBook-Pro.local:6b7f8929-6e5e-44e0-ab50-6a5486c49168", + "kind": "Sticky" + }, + "startToCloseTimeout": "20s", + "attempt": 1 + } + }, + { + "eventId": "92", + "eventTime": "2023-05-01T20:47:45.882142Z", + "eventType": "WorkflowTaskStarted", + "version": "0", + "taskId": "2042802", + "workerMayIgnore": false, + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "91", + "identity": "35932@Alexs-MacBook-Pro.local@", + "requestId": "2e5f3617-43a1-4333-9d36-e755e9f3c46c", + "suggestContinueAsNew": false, + "historySizeBytes": "17608" + } + }, + { + "eventId": "93", + "eventTime": "2023-05-01T20:47:45.883932Z", + "eventType": "WorkflowTaskCompleted", + "version": "0", + "taskId": "2042806", + "workerMayIgnore": false, + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "91", + "startedEventId": "92", + "identity": "35932@Alexs-MacBook-Pro.local@", + "binaryChecksum": "4c66ce02b6e876e584dcecdd9dbc34c0", + "workerVersioningId": null, + "sdkMetadata": null, + "meteringMetadata": null + } + }, + { + "eventId": "94", + "eventTime": "2023-05-01T20:48:19.938343Z", + "eventType": "ChildWorkflowExecutionCompleted", + "version": "0", + "taskId": "2045924", + "workerMayIgnore": false, + "childWorkflowExecutionCompletedEventAttributes": { + "result": null, + "namespace": "canary", + "namespaceId": "c9ffdbc7-b2f3-45c5-ba18-82be22e59f1d", + "workflowExecution": { + "workflowId": "temporal.canary.cron-workflow.sanity-2023-05-01T15:47:07-05:00/workflow.retry-activity", + "runId": "6398e686-2e21-4758-9c5c-09600ced52b1" + }, + "workflowType": { + "name": "workflow.retry-activity" + }, + "initiatedEventId": "18", + "startedEventId": "28" + } + }, + { + "eventId": "95", + "eventTime": "2023-05-01T20:48:19.938349Z", + "eventType": "WorkflowTaskScheduled", + "version": "0", + "taskId": "2045925", + "workerMayIgnore": false, + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Alexs-MacBook-Pro.local:6b7f8929-6e5e-44e0-ab50-6a5486c49168", + "kind": "Sticky" + }, + "startToCloseTimeout": "20s", + "attempt": 1 + } + }, + { + "eventId": "96", + "eventTime": "2023-05-01T20:48:19.942090Z", + "eventType": "WorkflowTaskStarted", + "version": "0", + "taskId": "2045929", + "workerMayIgnore": false, + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "95", + "identity": "35932@Alexs-MacBook-Pro.local@", + "requestId": "11a62905-6abd-4d3c-ba9e-9c92ba58a3f0", + "suggestContinueAsNew": false, + "historySizeBytes": "18138" + } + }, + { + "eventId": "97", + "eventTime": "2023-05-01T20:48:19.945148Z", + "eventType": "WorkflowTaskCompleted", + "version": "0", + "taskId": "2045933", + "workerMayIgnore": false, + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "95", + "startedEventId": "96", + "identity": "35932@Alexs-MacBook-Pro.local@", + "binaryChecksum": "4c66ce02b6e876e584dcecdd9dbc34c0", + "workerVersioningId": null, + "sdkMetadata": null, + "meteringMetadata": null + } + }, + { + "eventId": "98", + "eventTime": "2023-05-01T20:48:19.945169Z", + "eventType": "WorkflowExecutionCanceled", + "version": "0", + "taskId": "2045934", + "workerMayIgnore": false, + "workflowExecutionCanceledEventAttributes": { + "workflowTaskCompletedEventId": "97", + "details": null + } + } +] diff --git a/src/fixtures/events.continued-as-new.json b/src/fixtures/events.continued-as-new.json index e69de29bb..20394f909 100644 --- a/src/fixtures/events.continued-as-new.json +++ b/src/fixtures/events.continued-as-new.json @@ -0,0 +1,266 @@ +[ + { + "eventId": "5", + "eventTime": "2023-02-01T21:33:21.033103713Z", + "eventType": "WorkflowExecutionContinuedAsNew", + "version": "0", + "taskId": "1048853", + "workerMayIgnore": false, + "workflowExecutionContinuedAsNewEventAttributes": { + "newExecutionRunId": "85b7da2b-5144-43cd-9dce-729a3be71543", + "workflowType": { + "name": "SampleChildWorkflow" + }, + "taskQueue": { + "name": "child-workflow-continue-as-new", + "kind": "Normal" + }, + "input": { + "payloads": [4, 1] + }, + "workflowRunTimeout": "0s", + "workflowTaskTimeout": "10s", + "workflowTaskCompletedEventId": "4", + "backoffStartInterval": null, + "initiator": "Unspecified", + "failure": null, + "lastCompletionResult": null, + "header": { + "fields": {} + }, + "memo": null, + "searchAttributes": null + }, + "name": "WorkflowExecutionContinuedAsNew", + "id": "5", + "timestamp": "2023-02-01 UTC 21:33:21.03", + "classification": "New", + "category": "workflow", + "attributes": { + "type": "workflowExecutionContinuedAsNewEventAttributes", + "newExecutionRunId": "85b7da2b-5144-43cd-9dce-729a3be71543", + "workflowType": "SampleChildWorkflow", + "taskQueue": { + "name": "child-workflow-continue-as-new", + "kind": "Normal" + }, + "input": { + "payloads": [4, 1] + }, + "workflowRunTimeout": "", + "workflowTaskTimeout": "10 seconds", + "workflowTaskCompletedEventId": "4", + "backoffStartInterval": "", + "initiator": "Unspecified", + "failure": null, + "lastCompletionResult": null, + "header": { + "fields": {} + }, + "memo": null, + "searchAttributes": null + } + }, + { + "eventId": "4", + "eventTime": "2023-02-01T21:33:21.033086213Z", + "eventType": "WorkflowTaskCompleted", + "version": "0", + "taskId": "1048852", + "workerMayIgnore": false, + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "startedEventId": "3", + "identity": "5727@LauraWhakersMBP.lan@", + "binaryChecksum": "4d821097ce6ca5f72e7afc13f84d73db", + "workerVersioningId": null + }, + "name": "WorkflowTaskCompleted", + "id": "4", + "timestamp": "2023-02-01 UTC 21:33:21.03", + "classification": "Completed", + "category": "workflow", + "attributes": { + "type": "workflowTaskCompletedEventAttributes", + "scheduledEventId": "2", + "startedEventId": "3", + "identity": "5727@LauraWhakersMBP.lan@", + "binaryChecksum": "4d821097ce6ca5f72e7afc13f84d73db", + "workerVersioningId": null + } + }, + { + "eventId": "3", + "eventTime": "2023-02-01T21:33:21.024782672Z", + "eventType": "WorkflowTaskStarted", + "version": "0", + "taskId": "1048848", + "workerMayIgnore": false, + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "5727@LauraWhakersMBP.lan@", + "requestId": "8d70fac6-1630-4f02-845b-fcfd2ab45124", + "suggestContinueAsNew": false, + "historySizeBytes": "0" + }, + "name": "WorkflowTaskStarted", + "id": "3", + "timestamp": "2023-02-01 UTC 21:33:21.02", + "classification": "Started", + "category": "workflow", + "attributes": { + "type": "workflowTaskStartedEventAttributes", + "scheduledEventId": "2", + "identity": "5727@LauraWhakersMBP.lan@", + "requestId": "8d70fac6-1630-4f02-845b-fcfd2ab45124", + "suggestContinueAsNew": false, + "historySizeBytes": "0" + } + }, + { + "eventId": "2", + "eventTime": "2023-02-01T21:33:20.983864880Z", + "eventType": "WorkflowTaskScheduled", + "version": "0", + "taskId": "1048845", + "workerMayIgnore": false, + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "child-workflow-continue-as-new", + "kind": "Normal" + }, + "startToCloseTimeout": "10s", + "attempt": 1 + }, + "name": "WorkflowTaskScheduled", + "id": "2", + "timestamp": "2023-02-01 UTC 21:33:20.98", + "classification": "Scheduled", + "category": "workflow", + "attributes": { + "type": "workflowTaskScheduledEventAttributes", + "taskQueue": { + "name": "child-workflow-continue-as-new", + "kind": "Normal" + }, + "startToCloseTimeout": "10 seconds", + "attempt": 1 + } + }, + { + "eventId": "1", + "eventTime": "2023-02-01T21:33:20.020052838Z", + "eventType": "WorkflowExecutionStarted", + "version": "0", + "taskId": "1048838", + "workerMayIgnore": false, + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "SampleChildWorkflow" + }, + "parentWorkflowNamespace": "default", + "parentWorkflowNamespaceId": "7635a64b-dd16-407e-a7d3-9e72a97a51ba", + "parentWorkflowExecution": { + "workflowId": "parent-workflow_3eaa9ff1-3927-461d-94b4-271631094c8d", + "runId": "24240710-e6a1-4541-91b9-9f117bc407f4" + }, + "parentInitiatedEventId": "5", + "taskQueue": { + "name": "child-workflow-continue-as-new", + "kind": "Normal" + }, + "input": { + "payloads": [3, 2] + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "0s", + "workflowTaskTimeout": "10s", + "continuedExecutionRunId": "51e7c1a3-0205-4e11-8044-ce9e06bb3881", + "initiator": "Workflow", + "continuedFailure": null, + "lastCompletionResult": null, + "originalExecutionRunId": "4da1552a-97b7-46f5-8df0-8a09caaf5951", + "identity": "", + "firstExecutionRunId": "dd65e55d-d058-401c-9cb6-77dd4846a98e", + "retryPolicy": null, + "attempt": 1, + "workflowExecutionExpirationTime": null, + "cronSchedule": "", + "firstWorkflowTaskBackoff": "0.957939332s", + "memo": null, + "searchAttributes": null, + "prevAutoResetPoints": { + "points": [ + { + "binaryChecksum": "4d821097ce6ca5f72e7afc13f84d73db", + "runId": "dd65e55d-d058-401c-9cb6-77dd4846a98e", + "firstWorkflowTaskCompletedId": "4", + "createTime": "2023-02-01T21:33:18.002926045Z", + "expireTime": "2023-02-02T21:33:18.002943629Z", + "resettable": true + } + ] + }, + "header": { + "fields": {} + }, + "parentInitiatedEventVersion": "0" + }, + "name": "WorkflowExecutionStarted", + "id": "1", + "timestamp": "2023-02-01 UTC 21:33:20.02", + "classification": "Started", + "category": "workflow", + "attributes": { + "type": "workflowExecutionStartedEventAttributes", + "workflowType": "SampleChildWorkflow", + "parentWorkflowNamespace": "default", + "parentWorkflowNamespaceId": "7635a64b-dd16-407e-a7d3-9e72a97a51ba", + "parentWorkflowExecution": { + "workflowId": "parent-workflow_3eaa9ff1-3927-461d-94b4-271631094c8d", + "runId": "24240710-e6a1-4541-91b9-9f117bc407f4" + }, + "parentInitiatedEventId": "5", + "taskQueue": { + "name": "child-workflow-continue-as-new", + "kind": "Normal" + }, + "input": { + "payloads": [3, 2] + }, + "workflowExecutionTimeout": "", + "workflowRunTimeout": "", + "workflowTaskTimeout": "10 seconds", + "continuedExecutionRunId": "51e7c1a3-0205-4e11-8044-ce9e06bb3881", + "initiator": "Workflow", + "continuedFailure": null, + "lastCompletionResult": null, + "originalExecutionRunId": "4da1552a-97b7-46f5-8df0-8a09caaf5951", + "identity": "", + "firstExecutionRunId": "dd65e55d-d058-401c-9cb6-77dd4846a98e", + "retryPolicy": null, + "attempt": 1, + "workflowExecutionExpirationTime": "", + "cronSchedule": "", + "firstWorkflowTaskBackoff": "", + "memo": null, + "searchAttributes": null, + "prevAutoResetPoints": { + "points": [ + { + "binaryChecksum": "4d821097ce6ca5f72e7afc13f84d73db", + "runId": "dd65e55d-d058-401c-9cb6-77dd4846a98e", + "firstWorkflowTaskCompletedId": "4", + "createTime": "2023-02-01T21:33:18.002926045Z", + "expireTime": "2023-02-02T21:33:18.002943629Z", + "resettable": true + } + ] + }, + "header": { + "fields": {} + }, + "parentInitiatedEventVersion": "0" + } + } +] diff --git a/src/fixtures/large-number-of-child-workflows.json b/src/fixtures/large-number-of-child-workflows.json new file mode 100644 index 000000000..5b96e6e2b --- /dev/null +++ b/src/fixtures/large-number-of-child-workflows.json @@ -0,0 +1,759 @@ +{ + "executionConfig": { + "taskQueue": { + "name": "child-workflow", + "kind": "Normal" + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "0s", + "defaultWorkflowTaskTimeout": "10s" + }, + "workflowExecutionInfo": { + "execution": { + "workflowId": "parent-workflow_4609bb51-ec81-4a55-a943-0bf2ce924edb", + "runId": "15d3d584-12f0-4c2f-be72-869c6fa534be" + }, + "type": { + "name": "SampleParentWorkflow" + }, + "startTime": "2022-11-21T14:44:21.603149470Z", + "closeTime": null, + "status": "Running", + "historyLength": "9", + "parentNamespaceId": "", + "parentExecution": null, + "executionTime": "2022-11-21T14:44:21.603149470Z", + "memo": { + "fields": {} + }, + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "WyIzNjEyM2Y3ZDkzN2U5ODAzOTY3MmY1YmYyMDk2YzE0NiJd" + } + } + }, + "autoResetPoints": { + "points": [ + { + "binaryChecksum": "36123f7d937e98039672f5bf2096c146", + "runId": "15d3d584-12f0-4c2f-be72-869c6fa534be", + "firstWorkflowTaskCompletedId": "4", + "createTime": "2022-11-21T14:44:21.628934012Z", + "expireTime": null, + "resettable": true + } + ] + }, + "taskQueue": "child-workflow", + "stateTransitionCount": "6" + }, + "pendingActivities": [], + "pendingChildren": [ + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-6eb9dffb-05ba-4a9f-bcba-2e470cbb158d", + "runId": "ce481d0d-16e8-47a2-b155-f3f969bd8b01", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-3d433d5e-a946-42ed-ac3d-05be5809be06", + "runId": "439e60a7-e0d2-4745-9cdc-894d8b176a0b", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-0c25f305-44c5-4de3-8ba1-95a8176e45a6", + "runId": "17f33b96-f26d-46a9-b71f-0d95eb6c6f8a", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-4a2e0b29-715b-4484-8e60-d79b0f39cb5a", + "runId": "c2d4b23b-5c65-4a64-bfbe-a475e7cb1ca6", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-ec88751d-9720-4eee-92d0-fc313487711e", + "runId": "bcec6f73-5d27-4378-b12f-bd0dfe28ba9f", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-eb8c26de-d051-452f-8714-c157524f661c", + "runId": "941567cd-cef1-4c47-97e4-1ad2d0d90cdf", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-f7a0749c-a3ca-4cc3-9b71-c849e9018ce2", + "runId": "3644c379-4ed8-48c3-9444-c993edbe85dc", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-6162c299-9c6a-4194-8580-8c739d0f813b", + "runId": "aaf8367f-b481-4bf7-ba94-9d4964bfeb3a", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-9ebc037a-ad87-4dbc-b1b2-5a290d70a8a7", + "runId": "30ce4f43-392d-4be1-80be-dc2337c030ef", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-0e7ea652-e5ee-4f8c-bb06-031d9539ce04", + "runId": "02488550-407c-41c7-a123-95c2b3dc7c05", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-bdd95534-e48b-4b91-b5db-a12aa63b34ed", + "runId": "1dec1652-c205-455a-aff6-0937fb484e0d", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-5f02867f-610a-4d87-a364-97dd0b08f918", + "runId": "05f77e13-3d9e-4009-966b-a52c3a2fbceb", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-1b4be49f-c7f2-4922-b056-21c43d65123e", + "runId": "eb4f34e2-32d0-4fd2-a486-9987b44f259d", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-c3293f01-8aae-42cd-abdd-e222ac34ecba", + "runId": "6ba0d4f1-1528-450c-8783-114a9af03ef6", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-7b317fd4-76bb-47e4-b88f-6a65b458f2ac", + "runId": "d4cfdba0-2454-44a0-a5de-75df72844a76", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-884b75f8-cf51-4da8-b0c5-a96c3d45e385", + "runId": "c75f6c0f-a315-4289-a8c2-03c282497a82", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-1dd56f57-5486-4731-8a42-e29085b79caf", + "runId": "79a1524e-d681-4d82-a18c-6c71945d8d89", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-0fd57e59-572f-4e28-a13f-8c3717e2fdfc", + "runId": "6707a411-956e-4960-af33-ef91f16cf868", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-9a1f2c91-5b63-4e52-8d86-c56e0ae8e386", + "runId": "ac3f79c8-47ae-4c2f-9e53-31fbc00b4810", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-64a9ea61-c744-4d3e-846e-3ee142e0725a", + "runId": "7f1997b7-5639-4ff3-a0ad-9a1ca062dde7", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-b707f7c4-513a-480f-bafd-53ea1a801454", + "runId": "54775196-44c8-4aba-9dae-78a1892cbda1", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-f5c3bf7e-d7d4-4d8f-942b-3f5a2f76d278", + "runId": "89627585-6d38-498a-b76d-55f38e222fa0", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-6b92d048-d980-4736-8d9e-02b527704fa9", + "runId": "4330f8ab-9b76-412a-92dc-0afe75c2857e", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-46be225f-b3db-4725-aadf-9d2ea329bb2d", + "runId": "ef5286c7-a967-4d0d-9bbd-e98301dd8273", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-b57eea06-5ba8-4cd7-8478-0e365f6ecff1", + "runId": "c3811d5c-6ecd-4018-9772-12a12e7e8222", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-27222f14-a983-4cf3-99aa-175701051c58", + "runId": "ec122808-2508-4ccf-b7ce-22e6f463145b", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-49f23986-9095-4e09-8261-5100930c9364", + "runId": "4a2178c6-f68c-450d-93b9-32406bd73242", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-4df24994-072e-446c-868a-8be0a0de8b07", + "runId": "713e06f6-674c-4a3a-b93a-53c11621a752", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-68568e4d-c162-4b37-b779-0e032bf43253", + "runId": "cbcb62ea-b2a5-4791-a7f0-3c65e3c549f8", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-4b3d0484-0fda-4efd-be88-d334724dc5cc", + "runId": "83976707-6fe0-4b63-afc9-27c7c0168735", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-147dd7e5-1324-44c8-912b-0aecd617a2aa", + "runId": "6d3ee392-facc-4b44-a122-f2b320930abd", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-0e89f5e5-dee6-4bdf-83dd-2169c72b5d93", + "runId": "03816664-79c9-40a1-86fe-4ddd73a0dfd1", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-d60de2bf-fb1a-4aae-ae49-7d4816b00f64", + "runId": "5c7555c1-5e3f-4a3b-bb7c-773f189a423c", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-d2206d81-7c91-47fc-9fc2-3fb19451eaab", + "runId": "522a47f8-b588-4f84-82b1-1072e9644377", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-c396301a-945c-42d6-a626-1d6553aa2ea2", + "runId": "2cdfc476-1d33-427c-8de0-7309d5be9835", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-99fc93d1-a8fe-4628-97a3-dfa539d31e27", + "runId": "4c8e94ce-ad2a-4f1d-984b-5bd0998d7629", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-c3859b4a-8ca1-4a0d-b60c-5e7ef8672719", + "runId": "a3f36295-084b-45de-a426-f581444d5f5a", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-cded7ca3-2bd4-4046-ad13-f80d05da7ded", + "runId": "fabc990a-6931-4715-8c91-2009ad3c1816", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-ecbb87bf-820e-4dfd-8828-eb3e1b90bc97", + "runId": "947c0802-4d40-463c-a31a-5fd17a549c25", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-95cb9b26-746b-4874-a642-2195a09b7702", + "runId": "1433892f-f712-4492-9e52-d3f129fe9d23", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-2828dc2f-8896-4411-a74c-450751b1a956", + "runId": "c4d9d51f-732d-4528-af33-3cb597245f9f", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-099782cd-0c2d-4aa8-80d1-9b2c97459d53", + "runId": "094de832-d5ba-4e87-9760-16515b6f5c4f", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-93f63335-309e-4007-a88e-1c6050ddd2db", + "runId": "8e2fee73-677f-48f8-b9c5-d5c4a3b43129", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-6d15731a-1c9a-4b1f-9a2a-2db18c1f225f", + "runId": "85600be8-db7c-4c0d-bc3a-b5ea30e52f0b", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-7880ef6e-1df8-4f1f-b9cb-1e6f2fdada37", + "runId": "caab72c8-13ae-4f0b-9c0c-4510808fbd1d", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-442170d1-8eae-4354-bdb0-fd737028ef85", + "runId": "82ee9e9d-1a05-4d81-ae35-5bc429cfe599", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-993bd8ca-7b77-483d-913b-7f3157e744eb", + "runId": "957be67c-916d-4819-922e-f3481947348d", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-a50b9b27-56f4-4ceb-83fb-d557dd213cd3", + "runId": "204c4d87-4ad8-4d26-820b-92acf7c6b572", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-e5a2aafd-b927-4e42-8b98-8a9db4ac4e20", + "runId": "060f5026-89c2-4898-a331-62ac1093500d", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-acd6e03c-6df9-4484-9fb8-783a236c780b", + "runId": "ce80c0cf-9388-49d9-9b27-d9c421a21bb4", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-98d51bcc-32a9-45b7-90d1-dce6e7db035b", + "runId": "53aa5e8f-f8a4-485a-bbe2-1b83a32caf9b", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-9e3324cd-a9fa-44b1-953c-560c2addc9ab", + "runId": "58ed9f65-4fa6-40f5-8ca3-a2c184dddef2", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-6389fb14-fcca-4880-bb19-0cc19fcea0ed", + "runId": "0cc9b756-083a-44cd-bd9f-35d5a49e4a83", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-d79e01d0-fc8e-44ca-9adf-b9d4e5e4fc8f", + "runId": "49448e06-cdd0-4ee0-8797-2c937f2392ee", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-680a3540-573c-42a0-8d8e-1bbf66a2c8b3", + "runId": "321218c3-4675-42b7-8c23-7eadbcd70fbc", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-d16fa4f0-3a62-4980-8055-4d9463219f33", + "runId": "bacf2b9f-3fae-4493-a468-72bcc19c2f9d", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-5551ee2d-affd-4cce-9a17-9708e83fb9b6", + "runId": "67944706-3d3f-4560-a454-c163b54156c8", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-e1a24d9a-4794-49a1-8331-a906229c6ac1", + "runId": "1f9ac8cf-5a84-47cb-8638-6102a3d44905", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-cb45b77b-ece6-464f-af3a-4ae54c55d373", + "runId": "ecef8701-7583-4d37-b242-2a530cb71144", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-95aa840b-a8f5-4e43-8e0a-07c86fc8d2a6", + "runId": "d6fe8ecc-293a-4957-b40d-23d138703569", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-660f2e52-2703-4660-a315-f43b02447d8c", + "runId": "79d79f88-0161-4c14-8dab-0d57d66795cb", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-c8499050-0cf0-4a80-9575-73726214749e", + "runId": "21e1d342-1cd1-4f52-addb-33a19a314119", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-d9f7b679-3c52-4a66-b462-582dab7c4163", + "runId": "aac89522-68f2-4f01-8c56-60b8039de4c2", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-d7de60ec-ca07-4216-9021-d37b2e37c68f", + "runId": "de99ea6f-bf3d-4ca6-98af-99a337c3ae14", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-c3eb2cbc-9fc6-42a9-8e9c-b0b30510cd2a", + "runId": "8650c72b-1cad-4c01-b2fd-792c839ff12a", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-0b1a0146-880a-4fbe-9475-ba4e91a22d1e", + "runId": "1435ed85-40c9-4c2e-942e-5c24bbdff776", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-1b3a100b-29d4-4d99-930c-78cfb5d5a45e", + "runId": "e17ffc55-b1e8-4645-bbf7-593f2d75bc04", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-19da9605-210a-4e25-a6b7-913d7bf10989", + "runId": "8a660639-7cfd-4d30-836d-b018f5ee8bad", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-4e67b3a1-40e9-40b1-b111-28842751fc61", + "runId": "7fdb25b8-6bdd-4996-89c9-6f7a04c7b2b8", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-05313e7f-8c71-4135-84a5-8b1a8be128b2", + "runId": "479055a9-d72c-4cad-bb90-bd170805ab35", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-7c91eca6-53fb-4706-8d9d-19f41c4839f4", + "runId": "97728079-57da-4214-a3d3-2d61a2bf2866", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-183c4c38-1722-4153-b561-f7eff054d89b", + "runId": "81c68dd0-0c9a-40cc-8abf-67b3534f2630", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-ccab2c06-6b14-4f7d-9177-5d07bc449160", + "runId": "5e843c24-3bb8-4cbc-9579-8afd5f0f323a", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-a19e9cf5-55a2-4b42-8be0-13d7537ce6a6", + "runId": "7dff7cea-ecfb-457f-9042-c4077ff97d22", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-f74bbd12-f537-4166-ac29-ffa7ef4db922", + "runId": "07f34a89-0d10-4acb-9a9b-6bb1c125ac89", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-fdf42cda-4731-458a-9b7f-e6531e69d1de", + "runId": "f58bfcf1-acc3-40bf-912b-fd845e27715c", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-186e9844-71fe-4618-9c90-5e864a054774", + "runId": "22e46cae-7564-4bd5-9947-24a039404fd4", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-a8c78735-c9e1-4f03-8558-9a6166d23ff3", + "runId": "3d838982-27f3-4c34-bed7-8b73e570a151", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-c1fc2043-7ad0-4b07-b629-29e99a3e6151", + "runId": "ac3d27e0-4088-4ed3-9b84-5b48d9271d6f", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-d63d977d-b67c-4279-aa21-17636fa4e0af", + "runId": "881f476b-a8af-4e0d-9a9f-b1c250abf3c7", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-ce9b3eaa-45ff-4867-865b-ce62bf8af00e", + "runId": "366ef1f9-bb15-4b2b-beff-e9bb3a55e074", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-46873e19-9207-45e1-9e53-4de8e1fb32c4", + "runId": "4fd6667b-6d5d-4b31-9de3-a817d98a6fa9", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-8c220709-0d88-45bc-8cb2-89e5df50f8b2", + "runId": "2776545f-20c4-42ff-9ea2-bc2943982170", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-daae19a0-0407-4dcf-ba46-6307ce6c33b7", + "runId": "2bb9f819-7cb6-4835-abe5-d78faced17fa", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-c1c2bf98-bd5c-4cf8-b7a6-16699cbdfb0b", + "runId": "0aaa70cd-bc08-4391-9ee4-c3a8bd439a4f", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-9268bce6-25b8-4a89-88ab-d916219dbede", + "runId": "bd543d26-5c2d-485a-8c4d-85d2d7a01eee", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-b65a97e3-3ed7-423a-96e2-e861f1c6e7c9", + "runId": "976a53ab-f464-4b53-80d7-432d22d108e8", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-f08d6f17-c6a8-43fa-af89-f29690a21684", + "runId": "4dd94990-7dfd-4538-a6ab-21fc398ff8e0", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-ad890e59-a055-4a36-ba46-3b84e8550488", + "runId": "53e8e68c-44ac-481e-92e7-8095d184dde5", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-d1e9c3a7-7a10-4985-9d0e-f2821a69744a", + "runId": "238c16f4-5acf-4c40-b76e-5c841d5930ed", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-4a9c0bc7-18c1-47aa-ae5a-e53869387d70", + "runId": "fdb2439d-b2a8-4960-8776-f45589473f84", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-7244e955-ccdc-4e13-9a11-73c46bfdeeab", + "runId": "d03b6222-843a-489a-9e2d-d8bce92cd32a", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-c6e84a18-cab1-45f8-bca9-e4d457818b73", + "runId": "2eb13a34-6ff4-4d2e-a464-49a30753f20a", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-728851ba-0da3-40f6-85b4-6c7ef15a4759", + "runId": "938778c7-dd26-4103-8865-47b461240b9e", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-8bd8390c-e9d6-453c-bc9e-b3342d01705a", + "runId": "6f7b1d3f-e258-4187-8fc5-fe703cba883b", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-3b0413bb-603f-4e23-84e7-c730dfa0a99f", + "runId": "439ff0b2-8413-48f7-818b-40bc0a48573a", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-31c3722b-3673-40cb-aa59-0531667da344", + "runId": "632695c3-6fd1-4db0-81b8-7bc1a8e8a3be", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-8af91378-355b-4b3b-b232-76522ef99369", + "runId": "b0b3b4f7-1450-468a-95ba-cb47c0e16699", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-185f899c-8edb-4f1c-94a2-b93457f90126", + "runId": "6823f456-3901-4319-a574-1757ee2560c1", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + }, + { + "workflowId": "totally-fictional-child-worflow-with-an-intentionally-long-name-16980317-4d6f-4261-968b-119f159f1c5b", + "runId": "2f916461-511a-4223-81bc-bddd42950e1b", + "workflowTypeName": "TotallyRealChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + } + ], + "pendingWorkflowTask": null +} diff --git a/src/fixtures/local-activities/dotnet_local_activity.json b/src/fixtures/local-activities/dotnet_local_activity.json new file mode 100644 index 000000000..03581e568 --- /dev/null +++ b/src/fixtures/local-activities/dotnet_local_activity.json @@ -0,0 +1,36 @@ +{ + "eventId": "5", + "eventTime": "2024-01-16T09:45:41.869530419Z", + "eventType": "MarkerRecorded", + "version": "0", + "taskId": "1048644", + "workerMayIgnore": false, + "markerRecordedEventAttributes": { + "markerName": "core_local_activity", + "details": { + "data": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ewogICAgICAgICAgICAgICJzZXEiOiAxLAogICAgICAgICAgICAgICJhdHRlbXB0IjogMSwKICAgICAgICAgICAgICAiYWN0aXZpdHlfaWQiOiAiMSIsCiAgICAgICAgICAgICAgImFjdGl2aXR5X3R5cGUiOiAiRG9TdGF0aWNUaGluZyIsCiAgICAgICAgICAgICAgImNvbXBsZXRlX3RpbWUiOiB7CiAgICAgICAgICAgICAgICAic2Vjb25kcyI6IDE3MDUzOTgzNDEsCiAgICAgICAgICAgICAgICAibmFub3MiOiA4MDI1NjIwMDIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICJiYWNrb2ZmIjogbnVsbCwKICAgICAgICAgICAgICAib3JpZ2luYWxfc2NoZWR1bGVfdGltZSI6IHsKICAgICAgICAgICAgICAgICJzZWNvbmRzIjogMTcwNTM5ODM0MSwKICAgICAgICAgICAgICAgICJuYW5vcyI6IDgzMjQ3ODAwMAogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQ==" + } + ] + }, + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "some-static-value" + } + ] + } + }, + "workflowTaskCompletedEventId": "4", + "header": null, + "failure": null + } +} diff --git a/src/fixtures/local-activities/go_local_activity.json b/src/fixtures/local-activities/go_local_activity.json new file mode 100644 index 000000000..d660f3c43 --- /dev/null +++ b/src/fixtures/local-activities/go_local_activity.json @@ -0,0 +1,36 @@ +{ + "eventId": "5", + "eventTime": "2024-01-16T09:38:31.928219179Z", + "eventType": "MarkerRecorded", + "version": "0", + "taskId": "1048587", + "workerMayIgnore": false, + "markerRecordedEventAttributes": { + "markerName": "LocalActivity", + "details": { + "data": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ewogICAiQWN0aXZpdHlJRCI6ICIxIiwKICAgIkFjdGl2aXR5VHlwZSI6ICJBY3Rpdml0eSIsCiAgIlJlcGxheVRpbWUiOiAiMjAyNC0wMS0xNlQwOTozODozMS45MjE1MTQ0N1oiLAogICJBdHRlbXB0IjogMSwKICAiQmFja29mZiI6IDAKfQ==" + } + ] + }, + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "Hello Temporal!" + } + ] + } + }, + "workflowTaskCompletedEventId": "4", + "header": null, + "failure": null + } +} diff --git a/src/fixtures/local-activities/java_local_activity.json b/src/fixtures/local-activities/java_local_activity.json new file mode 100644 index 000000000..2f5c056db --- /dev/null +++ b/src/fixtures/local-activities/java_local_activity.json @@ -0,0 +1,86 @@ +{ + "eventId": "5", + "eventTime": "2024-01-16T09:40:05.230271305Z", + "eventType": "MarkerRecorded", + "version": "0", + "taskId": "1048605", + "workerMayIgnore": false, + "markerRecordedEventAttributes": { + "markerName": "LocalActivity", + "details": { + "activityId": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "9376db60-4eb5-3525-8f39-e61024fab1d1" + } + ] + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "Hello" + }, + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "World" + } + ] + }, + "meta": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": { + "firstSkd": 1705398005183, + "atpt": 1, + "backoff": null + } + } + ] + }, + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "Hello World!" + } + ] + }, + "time": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": 1705398005129 + } + ] + }, + "type": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImdyZWV0Ig==" + } + ] + } + }, + "workflowTaskCompletedEventId": "4", + "header": null, + "failure": null + } +} diff --git a/src/fixtures/local-activities/python_local_activity.json b/src/fixtures/local-activities/python_local_activity.json new file mode 100644 index 000000000..4b7944ed6 --- /dev/null +++ b/src/fixtures/local-activities/python_local_activity.json @@ -0,0 +1,36 @@ +{ + "eventId": "5", + "eventTime": "2024-01-16T09:40:53.696370675Z", + "eventType": "MarkerRecorded", + "version": "0", + "taskId": "1048598", + "workerMayIgnore": false, + "markerRecordedEventAttributes": { + "markerName": "core_local_activity", + "details": { + "data": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ewogICAgICAgICAgICAgICJzZXEiOiAxLAogICAgICAgICAgICAgICJhdHRlbXB0IjogMSwKICAgICAgICAgICAgICAiYWN0aXZpdHlfaWQiOiAiMSIsCiAgICAgICAgICAgICAgImFjdGl2aXR5X3R5cGUiOiAiY29tcG9zZV9ncmVldGluZyIsCiAgICAgICAgICAgICAgImNvbXBsZXRlX3RpbWUiOiB7CiAgICAgICAgICAgICAgICAic2Vjb25kcyI6IDE3MDUzOTgwNTMsCiAgICAgICAgICAgICAgICAibmFub3MiOiA2ODk1ODU5NjcKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICJiYWNrb2ZmIjogbnVsbCwKICAgICAgICAgICAgICAib3JpZ2luYWxfc2NoZWR1bGVfdGltZSI6IHsKICAgICAgICAgICAgICAgICJzZWNvbmRzIjogMTcwNTM5ODA1MywKICAgICAgICAgICAgICAgICJuYW5vcyI6IDY5NDkyMDAwMAogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQ==" + } + ] + }, + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "Hello, World!" + } + ] + } + }, + "workflowTaskCompletedEventId": "4", + "header": null, + "failure": null + } +} diff --git a/src/fixtures/local-activities/ts_local_activity.json b/src/fixtures/local-activities/ts_local_activity.json new file mode 100644 index 000000000..5cab88674 --- /dev/null +++ b/src/fixtures/local-activities/ts_local_activity.json @@ -0,0 +1,36 @@ +{ + "eventId": "5", + "eventTime": "2024-01-16T09:33:36.709036083Z", + "eventType": "MarkerRecorded", + "version": "0", + "taskId": "1048598", + "workerMayIgnore": false, + "markerRecordedEventAttributes": { + "markerName": "core_local_activity", + "details": { + "data": { + "payloads": [ + { + "metadata": { + "encoding": "json/plain" + }, + "data": "ewogICAgICAgICAgICAgICJzZXEiOiAxLAogICAgICAgICAgICAgICJhdHRlbXB0IjogMSwKICAgICAgICAgICAgICAiYWN0aXZpdHlfaWQiOiAiMSIsCiAgICAgICAgICAgICAgImFjdGl2aXR5X3R5cGUiOiAiZ3JlZXQiLAogICAgICAgICAgICAgICJjb21wbGV0ZV90aW1lIjogewogICAgICAgICAgICAgICAgInNlY29uZHMiOiAxNzA1Mzk3NjE2LAogICAgICAgICAgICAgICAgIm5hbm9zIjogNjY1NzkwMzc2CiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAiYmFja29mZiI6IG51bGwsCiAgICAgICAgICAgICAgIm9yaWdpbmFsX3NjaGVkdWxlX3RpbWUiOiB7CiAgICAgICAgICAgICAgICAic2Vjb25kcyI6IDE3MDUzOTc2MTYsCiAgICAgICAgICAgICAgICAibmFub3MiOiA2OTc1OTAwMDAKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0=" + } + ] + }, + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "Hello, Temporal!" + } + ] + } + }, + "workflowTaskCompletedEventId": "4", + "header": null, + "failure": null + } +} diff --git a/src/fixtures/settings.json b/src/fixtures/settings.json index b9f88d0b2..9008efafa 100644 --- a/src/fixtures/settings.json +++ b/src/fixtures/settings.json @@ -1,9 +1,16 @@ { - "Auth": { "Enabled": false, "Options": null }, + "Auth": { + "Enabled": false, + "Options": null + }, + "BannerText": "", "DefaultNamespace": "default", "ShowTemporalSystemNamespace": false, "FeedbackURL": "", "NotifyOnNewVersion": true, - "Codec": { "Endpoint": "", "PassAccessToken": true }, + "Codec": { + "Endpoint": "", + "PassAccessToken": true + }, "Version": "2.0.0" } diff --git a/src/fixtures/task-queue-rules.json b/src/fixtures/task-queue-rules.json new file mode 100644 index 000000000..2274727f9 --- /dev/null +++ b/src/fixtures/task-queue-rules.json @@ -0,0 +1,59 @@ +{ + "assignmentRules": [ + { + "rule": { + "targetBuildId": "1.12-alpha", + "percentageRamp": { + "rampPercentage": 0.2 + } + }, + "createTime": "2024-04-04T00:00:00Z" + }, + { + "rule": { + "targetBuildId": "1.11-beta", + "percentageRamp": { + "rampPercentage": 0.4 + } + }, + "createTime": "2024-04-02T00:00:00Z" + }, + { + "rule": { + "targetBuildId": "1.1", + "percentageRamp": { + "rampPercentage": 1 + } + }, + "createTime": "2024-04-01T00:00:00Z" + }, + { + "rule": { + "targetBuildId": "1.01", + "percentageRamp": { + "rampPercentage": 1 + } + }, + "createTime": "2024-03-15T00:00:00Z" + }, + { + "rule": { + "targetBuildId": "1.00", + "percentageRamp": { + "rampPercentage": 1 + } + }, + "createTime": "2024-03-01T00:00:00Z" + } + ], + "compatibleRedirectRules": [ + { + "rule": { + "sourceBuildId": "1.12", + "targetBuildId": "1.13" + }, + "createTime": "2024-01-01T00:00:00Z" + } + ], + "conflictToken": "abcd1234" +} diff --git a/src/fixtures/workflow.continued-as-new.json b/src/fixtures/workflow.continued-as-new.json index e69de29bb..0317ad153 100644 --- a/src/fixtures/workflow.continued-as-new.json +++ b/src/fixtures/workflow.continued-as-new.json @@ -0,0 +1,61 @@ +{ + "executionConfig": { + "taskQueue": { + "name": "child-workflow-continue-as-new", + "kind": "Normal" + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "300s", + "defaultWorkflowTaskTimeout": "20s" + }, + "workflowExecutionInfo": { + "execution": { + "workflowId": "child_workflow:24240710-e6a1-4541-91b9-9f117bc407f4", + "runId": "4da1552a-97b7-46f5-8df0-8a09caaf5951" + }, + "type": { + "name": "workflow.retry-workflow.ext" + }, + "startTime": "2023-02-01T21:33:20.020052838Z", + "closeTime": "2023-02-01T21:33:21.033103713Z", + "status": "ContinuedAsNew", + "historyLength": "7", + "parentNamespaceId": "7635a64b-dd16-407e-a7d3-9e72a97a51ba", + "parentExecution": { + "workflowId": "parent-workflow_3eaa9ff1-3927-461d-94b4-271631094c8d", + "runId": "24240710-e6a1-4541-91b9-9f117bc407f4" + }, + "executionTime": "2022-07-01T20:22:50.015107253Z", + "memo": { + "fields": {} + }, + "searchAttributes": { + "indexedFields": { + "TemporalChangeVersion": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "WyJpbml0aWFsIHZlcnNpb24tMyJd" + } + } + }, + "autoResetPoints": { + "points": [ + { + "binaryChecksum": "e56c0141e58df0bd405138565d0526f9", + "runId": "babf5e35-3a61-48c2-8738-a19293205282", + "firstWorkflowTaskCompletedId": "4", + "createTime": "2022-07-01T20:22:49.014356253Z", + "expireTime": "2022-07-11T20:22:49.015107253Z", + "resettable": true + } + ] + }, + "taskQueue": "canary-task-queue", + "stateTransitionCount": "6" + }, + "pendingActivities": [], + "pendingChildren": [], + "pendingWorkflowTask": null +} diff --git a/src/fixtures/workflow.pending-children.json b/src/fixtures/workflow.pending-children.json new file mode 100644 index 000000000..3c3198727 --- /dev/null +++ b/src/fixtures/workflow.pending-children.json @@ -0,0 +1,66 @@ +{ + "executionConfig": { + "taskQueue": { + "name": "child-workflow-continue-as-new", + "kind": "Normal" + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "300s", + "defaultWorkflowTaskTimeout": "20s" + }, + "workflowExecutionInfo": { + "execution": { + "workflowId": "parent-workflow_b84f7ba9-e645-4324-b300-77028d21c116", + "runId": "e50934b8-fad0-414c-93dd-d260a2476bc0" + }, + "type": { + "name": "workflow.retry-workflow.ext" + }, + "startTime": "2023-02-01T20:49:11.358822126Z", + "closeTime": null, + "status": "Running", + "historyLength": "7", + "parentNamespaceId": "", + "parentExecution": null, + "executionTime": "2022-07-01T20:22:50.015107253Z", + "memo": { + "fields": {} + }, + "searchAttributes": { + "indexedFields": { + "TemporalChangeVersion": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "WyJpbml0aWFsIHZlcnNpb24tMyJd" + } + } + }, + "autoResetPoints": { + "points": [ + { + "binaryChecksum": "e56c0141e58df0bd405138565d0526f9", + "runId": "babf5e35-3a61-48c2-8738-a19293205282", + "firstWorkflowTaskCompletedId": "4", + "createTime": "2022-07-01T20:22:49.014356253Z", + "expireTime": "2022-07-11T20:22:49.015107253Z", + "resettable": true + } + ] + }, + "taskQueue": "canary-task-queue", + "stateTransitionCount": "6" + }, + "pendingActivities": [], + "pendingChildren": [ + { + "workflowId": "child_workflow:e50934b8-fad0-414c-93dd-d260a2476bc0", + "runId": "7eb6a801-bae1-409e-98d8-10c38816eb0d", + "workflowTypeName": "SampleChildWorkflow", + "initiatedId": "5", + "parentClosePolicy": "Terminate" + } + ], + "pendingWorkflowTask": null +} diff --git a/src/fixtures/workflow.scheduled.json b/src/fixtures/workflow.scheduled.json new file mode 100644 index 000000000..bc9971a1f --- /dev/null +++ b/src/fixtures/workflow.scheduled.json @@ -0,0 +1,61 @@ +{ + "executionConfig": { + "taskQueue": { + "name": "child-workflow-continue-as-new", + "kind": "Normal" + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "300s", + "defaultWorkflowTaskTimeout": "20s" + }, + "workflowExecutionInfo": { + "execution": { + "workflowId": "child_workflow:24240710-e6a1-4541-91b9-9f117bc407f4", + "runId": "4da1552a-97b7-46f5-8df0-8a09caaf5951" + }, + "type": { + "name": "workflow.retry-workflow.ext" + }, + "startTime": "2023-02-01T21:33:20.020052838Z", + "closeTime": "2023-02-01T21:33:21.033103713Z", + "status": "ContinuedAsNew", + "historyLength": "7", + "parentNamespaceId": "7635a64b-dd16-407e-a7d3-9e72a97a51ba", + "parentExecution": { + "workflowId": "parent-workflow_3eaa9ff1-3927-461d-94b4-271631094c8d", + "runId": "24240710-e6a1-4541-91b9-9f117bc407f4" + }, + "executionTime": "2022-07-01T20:22:50.015107253Z", + "memo": { + "fields": {} + }, + "searchAttributes": { + "indexedFields": { + "TemporalScheduledById": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "InNpZ25hbC1ldmVyeS0zMCI=" + } + } + }, + "autoResetPoints": { + "points": [ + { + "binaryChecksum": "e56c0141e58df0bd405138565d0526f9", + "runId": "babf5e35-3a61-48c2-8738-a19293205282", + "firstWorkflowTaskCompletedId": "4", + "createTime": "2022-07-01T20:22:49.014356253Z", + "expireTime": "2022-07-11T20:22:49.015107253Z", + "resettable": true + } + ] + }, + "taskQueue": "canary-task-queue", + "stateTransitionCount": "6" + }, + "pendingActivities": [], + "pendingChildren": [], + "pendingWorkflowTask": null +} diff --git a/src/global.d.ts b/src/global.d.ts index 87e9528c2..02ac537f1 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,131 +1,10 @@ /// -declare module '@crownframework/svelte-error-boundary'; declare module '@sveltejs/svelte-virtual-list'; -type NamespaceItem = { - namespace: string; - href: (namspace: string) => string; - onClick: (namspace: string) => void; -}; - -type Optional = Omit & - Partial>; - -interface Window { - Prism: { - highlightAll: () => void; - highlightElement: (element: Element) => void; - }; +declare namespace svelte.JSX { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface HTMLAttributes { + 'onclick-outside': (e: CustomEvent) => void; + } } - -type Eventual = T | PromiseLike; - -type NamespaceScopedRequest = { namespace: string }; - -type NextPageToken = Uint8Array | string; -type WithNextPageToken = { nextPageToken?: NextPageToken }; -type WithoutNextPageToken = Omit; -type NextPageTokens = { - open: NextPageToken; - closed: NextPageToken; -}; - -type PaginationCallbacks = { - onStart?: () => void; - onUpdate?: ( - full: WithoutNextPageToken, - current: WithoutNextPageToken, - ) => void; - onComplete?: (finalProps: WithoutNextPageToken) => void; - onError?: (error: unknown) => void; -}; - -interface NetworkError { - statusCode: number; - statusText: string; - response: Response; - message?: string; -} - -type Settings = { - auth: { - enabled: boolean; - options: string[]; - }; - baseUrl: string; - codec: { - endpoint?: string; - passAccessToken?: boolean; - }; - defaultNamespace: string; - disableWriteActions: boolean; - showTemporalSystemNamespace: boolean; - notifyOnNewVersion: boolean; - feedbackURL: string; - runtimeEnvironment: { - isCloud: boolean; - isLocal: boolean; - envOverride: boolean; - }; - version: string; -}; - -type User = { - accessToken?: string; - idToken?: string; - name?: string; - given_name?: string; - family_name?: string; - middle_name?: string; - nickname?: string; - preferred_username?: string; - profile?: string; - picture?: string; - website?: string; - email?: string; - email_verified?: boolean; - gender?: string; - birthdate?: string; - zoneinfo?: string; - locale?: string; - phone_number?: string; - phone_number_verified?: boolean; - address?: string; - updated_at?: string; - sub?: string; - //eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; -}; -type ClusterInformation = import('$types').GetClusterInfoResponse; - -type TimeFormat = 'UTC' | 'relative' | 'local'; - -type SelectOptionValue = number | string | boolean; - -type BooleanString = 'true' | 'false'; - -type OptionLabel = { - label: string; - option?: string; -}; - -type UiVersionInfo = { - current: string; - recommended: string; -}; - -type DataEncoderStatus = 'notRequested' | 'success' | 'error'; - -type Color = - | 'blue' - | 'blueGray' - | 'gray' - | 'orange' - | 'red' - | 'green' - | 'red' - | 'indigo' - | 'yellow' - | 'purple' - | 'pink'; diff --git a/src/histoire.css b/src/histoire.css deleted file mode 100644 index 81c9e516e..000000000 --- a/src/histoire.css +++ /dev/null @@ -1,8 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@100;200;300;400;500;600;700;800;900&display=swap'); -@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap'); -@import url('/src/lib/vendor/css/normalize.css'); -@import url('/src/lib/vendor/prism/prism.css'); - -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/src/histoire.setup.ts b/src/histoire.setup.ts deleted file mode 100644 index 8c55ee77a..000000000 --- a/src/histoire.setup.ts +++ /dev/null @@ -1,2 +0,0 @@ -import '../src/lib/vendor/prism/prism.js'; -import './histoire.css'; diff --git a/src/hooks.server.ts b/src/hooks.server.ts new file mode 100644 index 000000000..801f25999 --- /dev/null +++ b/src/hooks.server.ts @@ -0,0 +1,7 @@ +import type { Handle } from '@sveltejs/kit'; + +export const handle: Handle = async ({ event, resolve }) => { + const response = await resolve(event, {}); + + return response; +}; diff --git a/src/hooks.ts b/src/hooks.ts deleted file mode 100644 index 083784472..000000000 --- a/src/hooks.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Handle } from '@sveltejs/kit'; - -export const handle: Handle = async ({ event, resolve }) => { - const response = await resolve(event, { - ssr: false, - }); - - return response; -}; diff --git a/src/lib/components/activity/activity-commands.svelte b/src/lib/components/activity/activity-commands.svelte new file mode 100644 index 000000000..e55044a08 --- /dev/null +++ b/src/lib/components/activity/activity-commands.svelte @@ -0,0 +1,119 @@ + + +
+ + + + + + + + + +
+ + + + + + + +{#key optionsUpdateDrawerOpen} + +{/key} diff --git a/src/lib/components/activity/activity-options-update-drawer.svelte b/src/lib/components/activity/activity-options-update-drawer.svelte new file mode 100644 index 000000000..f08f7f250 --- /dev/null +++ b/src/lib/components/activity/activity-options-update-drawer.svelte @@ -0,0 +1,317 @@ + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ + +
+ +
+
diff --git a/src/lib/components/activity/activity-pause-confirmation-modal.svelte b/src/lib/components/activity/activity-pause-confirmation-modal.svelte new file mode 100644 index 000000000..86bc6f722 --- /dev/null +++ b/src/lib/components/activity/activity-pause-confirmation-modal.svelte @@ -0,0 +1,79 @@ + + + +

+ {translate('activities.pause-modal-confirmation', { + activityId: activity.id, + })} +

+
+

{translate('activities.pause-modal-description')}

+ + +
+
diff --git a/src/lib/components/activity/activity-reset-confirmation-modal.svelte b/src/lib/components/activity/activity-reset-confirmation-modal.svelte new file mode 100644 index 000000000..3d9e83e60 --- /dev/null +++ b/src/lib/components/activity/activity-reset-confirmation-modal.svelte @@ -0,0 +1,80 @@ + + + +

+ {translate('activities.reset-modal-confirmation', { + activityId: activity.id, + })} +

+
+

{translate('activities.reset-modal-description')}

+ + +
+
diff --git a/src/lib/components/activity/activity-unpause-confirmation-modal.svelte b/src/lib/components/activity/activity-unpause-confirmation-modal.svelte new file mode 100644 index 000000000..71086569b --- /dev/null +++ b/src/lib/components/activity/activity-unpause-confirmation-modal.svelte @@ -0,0 +1,68 @@ + + + +

+ {translate('activities.pause-modal-confirmation', { + activityId: activity.id, + })} +

+
+

{translate('activities.unpause-modal-description')}

+ +
+
diff --git a/src/lib/components/advanced-visibility-guard.svelte b/src/lib/components/advanced-visibility-guard.svelte new file mode 100644 index 000000000..d22795275 --- /dev/null +++ b/src/lib/components/advanced-visibility-guard.svelte @@ -0,0 +1,9 @@ + + +{#if $supportsAdvancedVisibility} + +{:else} + +{/if} diff --git a/src/lib/components/api-error.svelte b/src/lib/components/api-error.svelte new file mode 100644 index 000000000..3c27fab9c --- /dev/null +++ b/src/lib/components/api-error.svelte @@ -0,0 +1,62 @@ + + +
+ +
+

+ {error.userMessage || 'An unexpected error occurred.'} +

+ + {#if error.isTemporary} +

+ This appears to be a temporary issue. Please try again in a few + moments. +

+ {/if} + + {#if error.isRetryable && retryCount < maxRetries} +
+ + {#if retryCount > 0} + + Attempt {retryCount + 1} of {maxRetries + 1} + + {/if} +
+ {:else if retryCount >= maxRetries} +
+

Unable to complete operation after {maxRetries + 1} attempts.

+

+ Please refresh the page or contact support if the problem persists. +

+
+ {/if} +
+
+
diff --git a/src/lib/components/auto-refresh-workflow.svelte b/src/lib/components/auto-refresh-workflow.svelte index d19689d78..94f060bc2 100644 --- a/src/lib/components/auto-refresh-workflow.svelte +++ b/src/lib/components/auto-refresh-workflow.svelte @@ -1,7 +1,7 @@ - - - + + diff --git a/src/lib/components/banner/banner-temporal-version.svelte b/src/lib/components/banner/banner-temporal-version.svelte deleted file mode 100644 index 1dd7b95c8..000000000 --- a/src/lib/components/banner/banner-temporal-version.svelte +++ /dev/null @@ -1,38 +0,0 @@ - - -{#if show} - -{/if} diff --git a/src/lib/components/banner/banner-ui-version.svelte b/src/lib/components/banner/banner-ui-version.svelte deleted file mode 100644 index 1a8e1e00c..000000000 --- a/src/lib/components/banner/banner-ui-version.svelte +++ /dev/null @@ -1,28 +0,0 @@ - - -{#if show} - -{/if} diff --git a/src/lib/components/banner/banner.svelte b/src/lib/components/banner/banner.svelte deleted file mode 100644 index 6fc048a67..000000000 --- a/src/lib/components/banner/banner.svelte +++ /dev/null @@ -1,49 +0,0 @@ - - -{#if show} -
- - {message} - - -
-{/if} - - diff --git a/src/lib/components/banner/banners.svelte b/src/lib/components/banner/banners.svelte deleted file mode 100644 index bb599b465..000000000 --- a/src/lib/components/banner/banners.svelte +++ /dev/null @@ -1,19 +0,0 @@ - - -{#if notifyOnNewVersion} - {#if shownBanner === BannersState.TemporalVersion} - - {:else if shownBanner === BannersState.UIVersion} - - {/if} -{/if} diff --git a/src/lib/components/batch-operations/details.svelte b/src/lib/components/batch-operations/details.svelte new file mode 100644 index 000000000..82bfe1a95 --- /dev/null +++ b/src/lib/components/batch-operations/details.svelte @@ -0,0 +1,66 @@ + + +
+

{translate('batch.details')}

+
+

+ {translate('batch.operation-type')} +

+

{operation.operationType}

+
+
+

+ {translate('batch.identity')} +

+

{operation.identity}

+
+
+

{translate('common.reason')}

+

{operation.reason}

+
+
+

{translate('common.start-time')}

+

+ {formatDate(operation.startTime, $timeFormat, { + relative: $relativeTime, + })} +

+
+
+

{translate('common.close-time')}

+

+ {formatDate(operation.closeTime, $timeFormat, { + relative: $relativeTime, + })} +

+
+
+

+ {translate('batch.total-operations')} +

+

+ {Intl.NumberFormat('en-US').format(operation.totalOperationCount)} +

+
+
+ + diff --git a/src/lib/components/batch-operations/header.svelte b/src/lib/components/batch-operations/header.svelte new file mode 100644 index 000000000..2b7434177 --- /dev/null +++ b/src/lib/components/batch-operations/header.svelte @@ -0,0 +1,61 @@ + + +
+
+
+

+ {translate('batch.describe-page-title')} +

+ + {operation.state} + +
+

+ {operation.jobId} +

+
+ {#if operation.state === 'Running'} + + + + {/if} +
diff --git a/src/lib/components/batch-operations/results.svelte b/src/lib/components/batch-operations/results.svelte new file mode 100644 index 000000000..d787cc6aa --- /dev/null +++ b/src/lib/components/batch-operations/results.svelte @@ -0,0 +1,82 @@ + + +
+

{translate('batch.results')}

+
+
+ + {translate('batch.operations-progress', { + percent: progressPercent, + })} + + + {Intl.NumberFormat('en-US').format(operation.completeOperationCount)} / {Intl.NumberFormat( + 'en-US', + ).format(operation.totalOperationCount)} + +
+
+
+
+
+ +
+
+ {translate('batch.operations-succeeded', { + count: operation.completeOperationCount, + })} + 0} + >{translate('batch.operations-failed', { + count: operation.failureOperationCount, + })} +
+
+
+
+
+
+
diff --git a/src/lib/components/batch-operations/table.svelte b/src/lib/components/batch-operations/table.svelte new file mode 100644 index 000000000..df8da6fd0 --- /dev/null +++ b/src/lib/components/batch-operations/table.svelte @@ -0,0 +1,67 @@ + + + + + + + + + + + {#each operations as { state, jobId, startTime, closeTime }} + + + + + + + {:else} + + + + {/each} +
+ {translate('batch.list-page-title')} +
{translate('common.status')}{translate('common.job-id')}{translate('common.start-time')}{translate('common.close-time')} + + {state} + + {jobId}{formatDate(startTime, $timeFormat, { + relative: $relativeTime, + })}{formatDate(closeTime, $timeFormat, { + relative: $relativeTime, + })} + +
diff --git a/src/lib/components/bottom-nav-links.svelte b/src/lib/components/bottom-nav-links.svelte new file mode 100644 index 000000000..d48407929 --- /dev/null +++ b/src/lib/components/bottom-nav-links.svelte @@ -0,0 +1,27 @@ + + +{#if open} +
+ {#each linkList as item} + {#if item.divider} +
+ {/if} + + {/each} +
+{/if} diff --git a/src/lib/components/bottom-nav-namespaces.svelte b/src/lib/components/bottom-nav-namespaces.svelte new file mode 100644 index 000000000..889044e43 --- /dev/null +++ b/src/lib/components/bottom-nav-namespaces.svelte @@ -0,0 +1,54 @@ + + +{#if open} +
+ +
    + {#each namespaces as { namespace, onClick }} +
  • + +
  • + {/each} +
+
+{/if} + + diff --git a/src/lib/components/bottom-nav-settings.svelte b/src/lib/components/bottom-nav-settings.svelte new file mode 100644 index 000000000..b127a066b --- /dev/null +++ b/src/lib/components/bottom-nav-settings.svelte @@ -0,0 +1,49 @@ + + +{#if open} +
+ + + +
+ + +
+{/if} diff --git a/src/lib/components/bottom-nav.svelte b/src/lib/components/bottom-nav.svelte new file mode 100644 index 000000000..91ac4e600 --- /dev/null +++ b/src/lib/components/bottom-nav.svelte @@ -0,0 +1,171 @@ + + + + +{#if menuIsOpen} +
+ + + + + + + +
+{/if} + + + diff --git a/src/lib/components/codec-endpoint-settings.svelte b/src/lib/components/codec-endpoint-settings.svelte new file mode 100644 index 000000000..9812a9fba --- /dev/null +++ b/src/lib/components/codec-endpoint-settings.svelte @@ -0,0 +1,44 @@ + + +
+
+ +
+
+
+ {#if !isValid} + {#if error} +

{error}

+ {/if} + + {/if} +
+ {#if maxLength && !disabled} + + {value?.length ?? 0} / {maxLength} + + {/if} +
diff --git a/src/lib/holocene/time-picker.svelte b/src/lib/holocene/time-picker.svelte index d2f26668d..56df9d4fa 100644 --- a/src/lib/holocene/time-picker.svelte +++ b/src/lib/holocene/time-picker.svelte @@ -1,33 +1,76 @@ -
+
+ 12 : parseInt(hour) > 23} + {disabled} + on:input={onInput} + /> 59)} + {disabled} + on:input={onInput} /> + {#if includeSeconds} + 59)} + {disabled} + on:input={onInput} + /> + {/if} + {#if twelveHourClock} + + (half = 'AM')} + >AM + (half = 'PM')} + >PM + + {/if}
diff --git a/src/lib/holocene/toast.svelte b/src/lib/holocene/toast.svelte index 773c6e6ca..aec2ba28a 100644 --- a/src/lib/holocene/toast.svelte +++ b/src/lib/holocene/toast.svelte @@ -1,48 +1,47 @@ -
-

+

- -
- - + +
diff --git a/src/lib/holocene/toaster.stories.svelte b/src/lib/holocene/toaster.stories.svelte new file mode 100644 index 000000000..c28187886 --- /dev/null +++ b/src/lib/holocene/toaster.stories.svelte @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + diff --git a/src/lib/holocene/toaster.story.svelte b/src/lib/holocene/toaster.story.svelte deleted file mode 100644 index 3abd8093b..000000000 --- a/src/lib/holocene/toaster.story.svelte +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - diff --git a/src/lib/holocene/toaster.svelte b/src/lib/holocene/toaster.svelte index 38cd00289..ab19f7b0f 100644 --- a/src/lib/holocene/toaster.svelte +++ b/src/lib/holocene/toaster.svelte @@ -1,117 +1,51 @@ - + import type { Toaster as Toast } from '../stores/toaster'; - -
- {#each topRightToasts as { message, variant, id } (id)} - - {message} +
+ {#each $toasts as { message, variant, id, link } (id)} + + {#if link} + + {message} + + {:else} + {message} + {/if} {/each}
-
- {#each bottomRightToasts as { message, variant, id } (id)} - - {message} - - {/each} -
-
- {#each bottomLeftToasts as { message, variant, id } (id)} - - {message} - - {/each} -
-
- {#each topLeftToasts as { message, variant, id } (id)} - - {message} - - {/each} -
- - diff --git a/src/lib/holocene/toggle-button/toggle-button.stories.svelte b/src/lib/holocene/toggle-button/toggle-button.stories.svelte new file mode 100644 index 000000000..24fe7580c --- /dev/null +++ b/src/lib/holocene/toggle-button/toggle-button.stories.svelte @@ -0,0 +1,83 @@ + + + + + + + diff --git a/src/lib/holocene/toggle-button/toggle-button.story.svelte b/src/lib/holocene/toggle-button/toggle-button.story.svelte deleted file mode 100644 index 60c9628b7..000000000 --- a/src/lib/holocene/toggle-button/toggle-button.story.svelte +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - (tab.a = 'auto')}>Auto - (tab.a = 'manual')}>Manual - - - - - - (tab.b = 'auto')}>Auto - (tab.b = 'manual')}>Manual - - - diff --git a/src/lib/holocene/toggle-button/toggle-button.svelte b/src/lib/holocene/toggle-button/toggle-button.svelte index 32cc0270c..25b800db8 100644 --- a/src/lib/holocene/toggle-button/toggle-button.svelte +++ b/src/lib/holocene/toggle-button/toggle-button.svelte @@ -1,73 +1,96 @@ -{#if href} - - {#if icon} -
- - -
- {:else} - - {/if} -
-{:else} -
+ + {#if icon}
- + {#if $$slots.default} + + {/if}
{:else} {/if} -
-{/if} + + diff --git a/src/lib/holocene/toggle-switch.stories.svelte b/src/lib/holocene/toggle-switch.stories.svelte new file mode 100644 index 000000000..64cc9a080 --- /dev/null +++ b/src/lib/holocene/toggle-switch.stories.svelte @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + diff --git a/src/lib/holocene/toggle-switch.story.svelte b/src/lib/holocene/toggle-switch.story.svelte deleted file mode 100644 index 817251fa2..000000000 --- a/src/lib/holocene/toggle-switch.story.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/src/lib/holocene/toggle-switch.svelte b/src/lib/holocene/toggle-switch.svelte index 9e3722c24..1d23f0974 100644 --- a/src/lib/holocene/toggle-switch.svelte +++ b/src/lib/holocene/toggle-switch.svelte @@ -1,31 +1,46 @@ + - - + import { twMerge as merge } from 'tailwind-merge'; - + diff --git a/src/lib/holocene/tooltip.stories.svelte b/src/lib/holocene/tooltip.stories.svelte new file mode 100644 index 000000000..14fbd5818 --- /dev/null +++ b/src/lib/holocene/tooltip.stories.svelte @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
whadup
+
whadup
+
whadup
+
+ +
+
diff --git a/src/lib/holocene/tooltip.story.svelte b/src/lib/holocene/tooltip.story.svelte deleted file mode 100644 index e95f69fb6..000000000 --- a/src/lib/holocene/tooltip.story.svelte +++ /dev/null @@ -1,94 +0,0 @@ - - - - -
- - - -
-
- - -
- - - -
-
- - -
- - - -
-
- - -
- - - -
-
- - -
- - - -
-
- - -
- - - -
-
- - -
- - - -
-
- - -
- - - -
-
- - -
- - - -
-
-
- - diff --git a/src/lib/holocene/tooltip.svelte b/src/lib/holocene/tooltip.svelte index 766d0d96a..b63357763 100644 --- a/src/lib/holocene/tooltip.svelte +++ b/src/lib/holocene/tooltip.svelte @@ -1,29 +1,86 @@ {#if hide} {:else} -
+
{/if} diff --git a/src/lib/holocene/user-menu-mobile.svelte b/src/lib/holocene/user-menu-mobile.svelte new file mode 100644 index 000000000..a6fa6887d --- /dev/null +++ b/src/lib/holocene/user-menu-mobile.svelte @@ -0,0 +1,25 @@ + + +{#if $authUser.accessToken} +
+ + +{/if} diff --git a/src/lib/holocene/user-menu.svelte b/src/lib/holocene/user-menu.svelte new file mode 100644 index 000000000..88fdb3081 --- /dev/null +++ b/src/lib/holocene/user-menu.svelte @@ -0,0 +1,58 @@ + + +{#if $authUser.accessToken} + + + {$authUser?.profile +
+ {#if $authUser?.name} +
+ {$authUser?.name.trim().charAt(0)} +
+ {/if} +
+
+ + +
+ +

{$authUser?.email}

+
+
+ +
+ + {translate('common.log-out')} +
+
+
+
+{/if} diff --git a/src/lib/holocene/zoom-svg.svelte b/src/lib/holocene/zoom-svg.svelte new file mode 100644 index 000000000..f80a694ad --- /dev/null +++ b/src/lib/holocene/zoom-svg.svelte @@ -0,0 +1,129 @@ + + +
+
+ + +
+ + + +
diff --git a/src/lib/i18n/index.ts b/src/lib/i18n/index.ts new file mode 100644 index 000000000..1152b1951 --- /dev/null +++ b/src/lib/i18n/index.ts @@ -0,0 +1,32 @@ +import type { Leaves } from '$lib/types/global'; + +import Locales from './locales'; + +/** + * https://www.i18next.com/translation-function/plurals#singular-plural + * when translating strings that could be singular or plural, i.e. "0 Apples" or "1 Apple" + * i18next expects the keys to be suffixed with `_zero`, `_one`, or `_other` for 0, 1, or n > 1 items respectively. + * If more suffixes are needed, i.e. `_few`, add them here. + */ +type WithoutPluralSuffix = T extends + | `${infer P}_zero` + | `${infer P}_one` + | `${infer P}_other` + ? P + : T; + +export const i18nNamespaces = Object.keys(Locales.en); + +export type I18nResources = typeof Locales.en; + +export type I18nKey = WithoutPluralSuffix< + Leaves<{ + [Key in keyof Resources]: Resources[Key]; + }> +>; + +// TODO: can we make this dynamic based on the namespace and key? +export type I18nReplace = { + count?: number; + [index: string]: string | number; +}; diff --git a/src/lib/i18n/locales/en/activities.ts b/src/lib/i18n/locales/en/activities.ts new file mode 100644 index 000000000..73c28b7a7 --- /dev/null +++ b/src/lib/i18n/locales/en/activities.ts @@ -0,0 +1,19 @@ +export const Namespace = 'activities' as const; + +export const Strings = { + 'pause-modal-confirmation': 'Pause Activity {{activityId}}', + 'pause-modal-description': 'Pause executing this Activity.', + 'unpause-modal-confirmation': 'Unpause Activity {{activityId}}', + 'unpause-modal-description': 'Resume executing this Activity.', + 'paused-since': 'Paused Since', + 'paused-by': 'Paused By', + 'pause-reason': 'Paused Reason', + 'reset-modal-confirmation': 'Reset Activity {{activityId}}', + 'reset-modal-description': + 'Reset the execution of this Activity back to the initial attempt.', + 'apply-to-all-activity-types': 'Apply changes to all {{type}} runs', + 'pause-all-activity-types': 'Pause all {{type}} runs', + 'unpause-all-activity-types': 'Unpause all {{type}} runs', + 'reset-heartbeat-details': 'Reset Heartbeat Details (optional)', + 'reset-success': 'Activity {{activityId}} has been reset successfully.', +}; diff --git a/src/lib/i18n/locales/en/batch.ts b/src/lib/i18n/locales/en/batch.ts new file mode 100644 index 000000000..f0f7a7dd2 --- /dev/null +++ b/src/lib/i18n/locales/en/batch.ts @@ -0,0 +1,23 @@ +export const Namespace = 'batch' as const; + +export const Strings = { + 'nav-title': 'Batch', + 'list-page-title': 'Batch Operations', + 'describe-page-title': 'Batch Operation', + 'empty-state-title': 'No Batch Operations', + 'back-link': 'Back to Batch Operations', + 'operation-type': 'Operation Type', + details: 'Operation Details', + identity: 'Identity', + 'total-operations': 'Total Operations', + 'operations-failed': '{{ count, number }} failed', + 'operations-succeeded': '{{ count, number }} succeeded', + 'operations-progress': '{{ percent }}% complete', + results: 'Operation Results', + 'max-concurrent-alert-title': 'Maximum concurrent Batch Operations met', + 'max-concurrent-alert-description': + 'Only 1 in progress Batch Operation is permitted. If you are attempting to create a new Batch Operation while there is one currently running, it will fail.', + 'job-id-input-hint': + 'Job ID must be unique. If left blank, a randomly generated UUID will be used.', + 'job-id-input-error': 'Job ID must only contain URL safe characters', +} as const; diff --git a/src/lib/i18n/locales/en/common.ts b/src/lib/i18n/locales/en/common.ts new file mode 100644 index 000000000..8b91707b0 --- /dev/null +++ b/src/lib/i18n/locales/en/common.ts @@ -0,0 +1,203 @@ +export const Namespace = 'common' as const; + +export const Strings = { + loading: 'Loading...', + filtering: 'filtering', + filter: 'Filter', + 'unknown-error': 'An unknown error occurred.', + search: 'Search', + apply: 'Apply', + remove: 'Remove', + query: 'Query', + ago: 'ago', + 'all-time': 'All Time', + time: 'Time', + custom: 'Custom', + 'start-time': 'Start Time', + 'end-time': 'End Time', + 'close-time': 'Close Time', + relative: 'Relative', + utc: 'UTC', + local: 'Local', + cancel: 'Cancel', + 'clear-all': 'Clear all', + 'clear-all-capitalized': 'Clear All', + 'ante-meridiem': 'AM', + 'post-meridiem': 'PM', + ascending: 'Ascending', + descending: 'Descending', + start: 'Start', + end: 'End', + true: 'True', + false: 'False', + after: 'After', + before: 'Before', + between: 'Between', + 'in-last': 'In Last', + 'starts-with': 'Starts with', + equals: 'Equals', + 'greater-than': 'Greater Than', + 'greater-than-or-equal-to': 'Greater than or equal to', + 'less-than': 'Less than', + 'less-than-or-equal-to': 'Less than or equal to', + 'is-null': 'Is null', + 'is-not-null': 'Is not null', + all: 'All', + submit: 'Submit', + reason: 'Reason', + 'reason-placeholder': 'Enter a reason', + identity: 'Identity', + 'identity-placeholder': 'Enter an identity', + confirm: 'Confirm', + summary: 'Summary', + scheduled: 'Scheduled', + 'show-all': 'Show all', + 'date-and-time': 'Date & Time', + event: 'Event', + 'collapse-all': 'Collapse All', + 'expand-all': 'Expand All', + maximize: 'Maximize', + minimize: 'Minimize', + id: 'ID', + yes: 'Yes', + no: 'No', + maybe: 'Maybe', + refresh: 'Refresh', + 'error-occurred': 'An Error Occurred', + save: 'Save', + 'workflow-type': 'Workflow Type', + 'workflow-id': 'Workflow ID', + 'run-id': 'Run ID', + 'task-queue': 'Task Queue', + preview: 'Preview', + status: 'Status', + created: 'Created: {{created}}', + 'last-updated': 'Last Updated: {{updated}}', + edit: 'Edit', + update: 'Update', + delete: 'Delete', + view: 'View', + memo: 'Memo', + notes: 'Notes', + add: 'Add', + 'from-now': 'from now', + 'hours-abbreviated': 'hrs', + 'minutes-abbreviated': 'min', + 'seconds-abbreviated': 'sec', + days: 'days', + 'number-input-placeholder': 'Enter a number', + enter: 'Enter', + 'no-results': 'No results', + 'type-or-paste-in': 'Type or paste in', + 'per-page': 'Per Page', + 'next-page': 'Next Page', + 'previous-page': 'Previous Page', + 'go-to-page': 'Go to page {{page}}', + 'next-row': 'Next Row', + 'previous-row': 'Previous Row', + today: 'Today', + close: 'Close', + deprecated: 'Deprecated', + namespaces: 'Namespaces', + cluster: 'Cluster', + 'codec-server': 'Codec Server', + workflows: 'Workflows', + 'workflows-plural_one': 'Workflow', + 'workflows-plural_other': 'Workflows', + schedules: 'Schedules', + 'schedules-plural_one': '{{ count }} Schedule', + 'schedules-plural_other': '{{ count }} Schedules', + archive: 'Archive', + import: 'Import', + feedback: 'Feedback', + 'log-out': 'Log out', + 'my-profile': 'My Profile', + details: 'Details', + unknown: 'Unknown', + key: 'Key', + type: 'Type', + disabled: 'Disabled', + enabled: 'Enabled', + primary: 'Primary', + previous: 'Previous', + next: 'Next', + name: 'Name', + version: 'Version', + main: 'Main', + 'select-all': 'Select All', + absolute: 'Absolute', + 'copy-icon-title': 'Click to copy content', + 'copy-success-icon-title': 'Content copied to clipboard', + 'filter-workflows': 'Filter workflows', + 'event-category-filter-label': 'Open event category filter menu', + 'event-date-filter-label': 'Open event date filter menu', + 'date-time-menu-label': 'Open time format menu', + 'workflow-filter-label': 'Open {{attribute}} filter menu', + 'workflow-status-filter-label': 'Open execution status filter menu', + 'clear-input-button-label': 'Clear input', + 'keyboard-shortcuts': 'Keyboard Shortcuts', + 'close-keyboard-shortcuts': 'Close keyboard shortcuts', + none: 'None', + 'user-profile': 'User Profile', + 'arrow-key-up': 'Arrow key up', + 'arrow-key-down': 'Arrow key down', + 'arrow-key-left': 'Arrow key left', + 'arrow-key-right': 'Arrow key right', + on: 'On', + off: 'Off', + labs: 'Labs', + experimental: 'Experimental', + timezone: 'Timezone {{ timezone }}', + 'time-unit': 'Time Unit', + 'time-format': 'Time Format', + 'time-range': 'Time Range', + 'based-on-time-preface': 'Based on', + 'download-json': 'Download JSON', + 'download-event-history-json': 'Download Event History JSON', + 'skip-nav': 'Skip to Main Content', + timeline: 'Timeline', + graph: 'Graph', + 'equal-to': 'Equal to', + 'not-equal-to': 'Not equal to', + 'encode-failed': 'Data encoding failed', + 'decode-failed': 'Data decoding failed', + 'job-id': 'Job ID', + 'auto-refresh': 'Auto refresh', + 'auto-refresh-tooltip': '{{ interval }} second page refresh', + 'view-more': 'View More...', + 'view-all': 'View All', + 'view-all-runs': 'View All Runs', + 'more-options': 'More options', + download: 'Download', + duration: 'Duration', + 'history-size-bytes': 'History Size (Bytes)', + 'execution-details': 'Execution Details', + day: 'Day', + night: 'Night', + 'system-default': 'System Default', + docs: 'Docs', + 'upload-error': 'Error uploading file', + description: 'Description', + active: 'Active', + inactive: 'Inactive', + 'page-not-found': 'Page Not Found', + value: 'Value', + table: 'Table', + failure: 'Failure', + 'pending-and-failed': 'Pending and Failed', + 'pending-and-failed-description': + 'View only Pending, Failed, and Timed Out events', + 'stack-trace': 'Stack Trace', + source: 'Source', + url: 'URL', + state: 'State', + attempt: 'Attempt', + message: 'Message', + 'upload-json': 'Upload JSON', + 'input-valid-json': 'Input must be valid JSON', + 'workflows-table': 'Workflows Table', + 'schedules-table': 'Schedules Table', + column: 'Column', + columns: 'Columns', + 'child-count': 'Child Count', +} as const; diff --git a/src/lib/i18n/locales/en/data-encoder.ts b/src/lib/i18n/locales/en/data-encoder.ts new file mode 100644 index 000000000..870c833c3 --- /dev/null +++ b/src/lib/i18n/locales/en/data-encoder.ts @@ -0,0 +1,30 @@ +export const Namespace = 'data-encoder' as const; + +export const Strings = { + 'codec-server': 'Codec Server', + 'endpoint-title': 'Codec Server browser endpoint', + 'endpoint-description': + 'Enter a Codec Server endpoint for this browser. This will be stored in your browser and will only be accessible by you.', + 'endpoint-placeholder': 'Paste your endpoint here', + 'pass-access-token-label': 'Pass the user access token', + 'include-cross-origin-credentials-label': 'Include cross-origin credentials', + 'include-cross-origin-credentials-warning': + 'Warning: Pre-flight checks will be done and could result in failure to decode if incorrectly configured.', + 'port-title': 'tctl plugin port ', + 'port-info': 'If both are set, the Codec Server endpoint will be used.', + 'access-token-https-error': + 'Endpoint must be https:// if passing access token', + 'prefix-error': 'Endpoint must start with http:// or https://', + 'codec-server-description-prefix': 'A ', + 'codec-server-description-suffix': + ' decodes your data. A Codec Server endpoint can be set at the {{level}} level, or locally in your browser.', + 'browser-override-description': + 'Use my browser setting and ignore {{level}}-level setting.', + 'no-browser-override-description': + 'Use {{level}}-level setting, where available.', + 'codec-server-configured': 'Codec Server is configured', + 'codec-server-error': 'Codec Server could not connect', + 'codec-server-success': 'Codec Server succesfully converted content', + 'configure-codec-server': 'Configure Codec Server', + 'encode-error': 'Codec Server failed to encode', +} as const; diff --git a/src/lib/i18n/locales/en/date-picker.ts b/src/lib/i18n/locales/en/date-picker.ts new file mode 100644 index 000000000..7578101d0 --- /dev/null +++ b/src/lib/i18n/locales/en/date-picker.ts @@ -0,0 +1,6 @@ +export const Namespace = 'date-picker' as const; + +export const Strings = { + 'next-month': 'Next Month', + 'previous-month': 'Previous Month', +} as const; diff --git a/src/lib/i18n/locales/en/deployments.ts b/src/lib/i18n/locales/en/deployments.ts new file mode 100644 index 000000000..0ae812a39 --- /dev/null +++ b/src/lib/i18n/locales/en/deployments.ts @@ -0,0 +1,44 @@ +export const Namespace = 'deployments' as const; + +export const Strings = { + deployments: 'Deployments', + deployment: 'Deployment', + 'worker-deployments': 'Worker Deployments', + 'worker-deployments-description': + 'A simple line that explains the power and relationship of Deployment IDs, Series, and Build IDs.', + 'worker-deployments-search-placeholder': 'Filter by Deployment Name', + 'go-to-workflows': 'Go to Workflows', + 'empty-state-title': 'No Worker Deployments', + 'error-message-fetching': 'Error fetching deployments', + 'empty-state-description': + 'Want to learn about versioning? . Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque vitae lacus eu mauris facilisis sodales.', + name: 'Deployment Name', + 'current-version': 'Current Version', + 'deployment-version': 'Deployment Version', + 'build-id': 'Build ID', + 'versioning-behavior': 'Versioning Behavior', + created: 'Created At', + deployed: 'Deployed At', + ramping: 'Ramping', + 'ramping-percentage': 'Ramping {{ percentage }}%', + workflows: 'Go to Workflows', + version: 'Version', + status: 'Status', + current: 'Current', + latest: 'Latest', + 'task-queue-name': 'Task Queue Name', + 'task-queue-type': 'Task Queue Type', + 'rollout-started': 'Rollout Started At', + 'rollout-completed': 'Rollout Completed At', + 'rollout-url': 'Rollout URL', + 'worker-id': 'Worker ID', + 'back-to-deployments': 'Back to Worker Deployments', + 'series-name': 'Series Name', + 'first-poller': 'First Poller', + 'last-poller': 'Last Poller', + 'first-task': 'First Task', + 'last-task': 'Last Task', + 'rollout-id': 'Rollout ID', + actions: 'Actions', + unversioned: 'No Current Version', +} as const; diff --git a/src/lib/i18n/locales/en/events.ts b/src/lib/i18n/locales/en/events.ts new file mode 100644 index 000000000..288fbd01e --- /dev/null +++ b/src/lib/i18n/locales/en/events.ts @@ -0,0 +1,97 @@ +export const Namespace = 'events' as const; + +export const Strings = { + 'empty-state-title': 'No Events Match', + 'empty-state-description': + 'There are no events that match your filters or selected view. Adjust your filters or view to see your events.', + 'group-empty-state-title': 'Event Group Not Found', + 'sort-ascending': 'Sort 1-9', + 'sort-descending': 'Sort 9-1', + 'date-and-time': 'Date & Time', + 'show-elapsed-time': 'Show Elapsed Time & Duration', + 'event-type': 'Event Type', + 'workflow-events': 'Workflow Events', + category: { + all: 'All', + activity: 'Activity', + 'activity-tooltip': + "An Activity is a normal function or method execution that's intended to execute a single, well-defined action.", + 'child-workflow': 'Child Workflow', + 'child-workflow-tooltip': + 'A Child Workflow Execution is a Workflow Execution that is spawned from within another Workflow in the same Namespace.', + command: 'Command', + 'command-tooltip': 'Command Events', + other: 'Other', + 'other-tooltip': + 'Marker Recorded, Upsert Search Attributes and Workflow Properties Modified.', + 'local-activity': 'Local Activity', + 'local-activity-tooltip': + 'A Local Activity is an Activity Execution that executes in the same process as the Workflow Execution that spawns it.', + nexus: 'Nexus', + 'nexus-tooltip': + 'Nexus connects durable executions within and across Namespaces.', + signal: 'Signal', + 'signal-tooltip': + 'Signals are asynchronous write requests. They cause changes in the running Workflow, but you cannot await any response or error.', + timer: 'Timer', + 'timer-tooltip': 'Timers are durable timers set for a fixed time period.', + update: 'Update', + 'update-tooltip': + 'Updates are synchronous, tracked write requests. The sender of the Update can wait for a response on completion or an error on failure.', + workflow: 'Workflow', + 'workflow-tooltip': + 'A Workflow Task is a Task that contains the context needed to make progress with a Workflow Execution.', + }, + 'attribute-group': { + activity: 'Activity', + parent: 'Parent', + 'retry-policy': 'Retry Policy', + schedule: 'Schedule', + 'search-attributes': 'Search Attributes', + summary: 'Summary', + 'task-queue': 'Task Queue', + workflow: 'Workflow', + }, + 'custom-search-attributes': 'Custom Search Attributes', + 'custom-search': 'custom search', + attribute: 'attribute', + 'event-group': 'Events related to {{eventName}}', + 'error-event': 'Error Event', + 'import-event-history': 'Import Event History', + 'import-event-history-file-upload': + 'Select an event history JSON file to upload', + 'event-history-view': 'Event History View', + 'api-history-link': 'View in Github', + 'history-expected-formats': 'Expected JSON formats', + 'event-history-import-error': 'Could not create event history from JSON', + 'event-history-load-error': 'Could not parse JSON', + 'event-classification-label': 'Event Classification', + 'event-classification': { + unspecified: 'Unspecified', + scheduled: 'Scheduled', + open: 'Open', + new: 'New', + started: 'Started', + initiated: 'Initiated', + running: 'Running', + completed: 'Completed', + fired: 'Fired', + cancelrequested: 'Cancel Requested', + timedout: 'Timed Out', + signaled: 'Signaled', + canceled: 'Canceled', + failed: 'Failed', + terminated: 'Terminated', + pending: 'Pending', + retrying: 'Retrying', + }, + 'decode-event-history': 'Decode Event History', + encoded: 'Encoded', + decoded: 'Decoded', + 'decoded-description': 'Codec Server decoded and base64 encoded', + readable: 'Human Readable', + 'readable-description': 'Codec Server decoded and base64 decoded', + 'event-types': 'Event Types', + 'decode-failed': 'Decoding failed', + 'view-raw-history': 'View Raw History', +} as const; diff --git a/src/lib/i18n/locales/en/index.ts b/src/lib/i18n/locales/en/index.ts new file mode 100644 index 000000000..da7094814 --- /dev/null +++ b/src/lib/i18n/locales/en/index.ts @@ -0,0 +1,33 @@ +import * as Activities from './activities'; +import * as Batch from './batch'; +import * as Common from './common'; +import * as DataEncoder from './data-encoder'; +import * as DatePicker from './date-picker'; +import * as Deployments from './deployments'; +import * as Events from './events'; +import * as Namespaces from './namespaces'; +import * as Nexus from './nexus'; +import * as Schedules from './schedules'; +import * as SearchAttributes from './search-attributes'; +import * as TypedErrors from './typed-errors'; +import * as Workers from './workers'; +import * as Workflows from './workflows'; + +export const EN = 'en' as const; + +export const English = { + [Activities.Namespace]: Activities.Strings, + [Batch.Namespace]: Batch.Strings, + [Common.Namespace]: Common.Strings, + [DatePicker.Namespace]: DatePicker.Strings, + [Deployments.Namespace]: Deployments.Strings, + [Workflows.Namespace]: Workflows.Strings, + [TypedErrors.Namespace]: TypedErrors.Strings, + [Events.Namespace]: Events.Strings, + [Schedules.Namespace]: Schedules.Strings, + [DataEncoder.Namespace]: DataEncoder.Strings, + [Namespaces.Namespace]: Namespaces.Strings, + [Nexus.Namespace]: Nexus.Strings, + [SearchAttributes.Namespace]: SearchAttributes.Strings, + [Workers.Namespace]: Workers.Strings, +} as const; diff --git a/src/lib/i18n/locales/en/namespaces.ts b/src/lib/i18n/locales/en/namespaces.ts new file mode 100644 index 000000000..7c4204709 --- /dev/null +++ b/src/lib/i18n/locales/en/namespaces.ts @@ -0,0 +1,29 @@ +export const Namespace = 'namespaces' as const; + +export const Strings = { + 'namespace-select-header': 'Select a Namespace', + 'namespace-select-empty-state': 'No Namespaces', + 'namespace-label': 'Select a Namespace', + 'namespaces-empty-state-title': 'No Namespaces Found', + 'namespaces-empty-state-content': + 'You do not have access to a Namespace. Contact your Administrator for assistance.', + namespace: 'Namespace', + versions: 'Versions', + owner: 'Owner', + global: 'Global', + 'retention-period': 'Retention Period', + 'history-archival': 'History Archival', + 'visibility-archival': 'Visibility Archival', + 'failover-version': 'Failover Version', + clusters: 'Clusters', + 'client-actions': 'Client Actions', + 'signal-workflow': 'Signal Workflow', + 'unauthorized-namespace-error': 'You do not have access to this namespace.', + 'select-namespace-welcome': 'Welcome to Temporal', + 'select-namespace': 'Select a Namespace to get started.', + 'search-namespaces': 'Search Namespaces', + 'select-namespace-empty-state': + 'No Namespaces. Contact your admin to create one.', + 'back-to-namespaces': 'Back to Namespaces', + 'go-to-namespace': 'Go to Namespace', +} as const; diff --git a/src/lib/i18n/locales/en/nexus.ts b/src/lib/i18n/locales/en/nexus.ts new file mode 100644 index 000000000..966026636 --- /dev/null +++ b/src/lib/i18n/locales/en/nexus.ts @@ -0,0 +1,68 @@ +export const Namespace = 'nexus' as const; + +export const Strings = { + nexus: 'Nexus', + 'nexus-endpoint': 'Nexus Endpoint | {{id}}', + endpoint: 'Endpoint', + endpoints: 'Nexus Endpoints', + 'all-endpoints': 'All Endpoints', + 'my-endpoints': 'My Endpoints', + 'back-to-endpoints': 'Back To Nexus Endpoints', + 'back-to-endpoint': 'Back To Nexus Endpoint', + 'create-endpoint': 'Create Nexus Endpoint', + 'endpoint-name': 'Endpoint Name', + 'endpoint-name-placeholder': 'Nexus Endpoint must have a unique name', + 'select-endpoint': 'Select a Endpoint', + 'task-queue-placeholder': 'Enter a Task Queue', + 'endpoint-alias': 'Endpoint Alias', + target: 'Target', + 'target-description': + 'Specify the target Namespace and task queue the worker will poll on.', + 'target-namespace': 'Target Namespace', + 'select-namespace': 'Select a Namespace', + 'nexus-description': + 'Add a link to your repo or instructions to help other users in this account use this endpoint.', + 'description-placeholder': + '//Provide a readme for users to use this endpoint', + handler: 'Handler', + 'delete-endpoint': 'Delete Endpoint', + 'delete-modal-title': 'Delete Nexus Endpoint?', + 'delete-modal-confirmation-preface': 'Are you sure you want to delete ', + 'delete-modal-confirmation-postface': + 'Any Workflows calling this endpoint will encounter failures.', + 'type-confirm-preface': 'Type ', + 'type-confirm-postface': 'to delete this endpoint.', + 'endpoint-name-hint': + 'Endpoint name must start with A-Z, a-z or _ and can only contain A-Z, a-z, 0-9, or _', + 'endpoint-name-hint-with-dash': + 'Endpoint name must start with A-Z or a-z and can only contain A-Z, a-z, 0-9 or -', + 'access-policy': 'Access Policy', + 'allowed-caller-namespaces': 'Allowed caller Namespaces', + 'allowed-caller-namespaces-description': + 'Namespace(s) that are allowed to call this Endpoint.', + 'select-namespaces': 'Select Namespace(s)', + 'selected-namespaces_one': '{{count}} Namespace selected', + 'selected-namespaces_other': '{{count}} Namespaces selected', + 'empty-state': 'No Nexus Endpoints found, try a new search.', + 'nexus-callback': 'Nexus Callback', + callback: { + standby: 'Nexus Callback is standing by, waiting to be triggered.', + scheduled: + 'Nexus Callback is in the queue waiting to be executed or is currently executing.', + 'backing-off': + 'Nexus Callback has failed with a retryable error and is backing off before the next attempt.', + failed: 'Nexus Callback has failed.', + succeeded: 'Nexus Callback has succeeded.', + }, + 'callback-url': 'Callback URL', + 'last-attempt-completed-time': 'Last Attempt Completed Time', + 'next-attempt-scheduled-time': 'Next Attempt Scheduled Time', + 'last-attempt-failure': 'Last Attempt Failure', + 'blocked-reason': 'Blocked Reason', + link: 'Link', + 'link-namespace': 'Link Namespace', + service: 'Service', + operation: 'Operation', + 'operation-token': 'Operation Token', + 'cancellation-info': 'Cancellation Info', +} as const; diff --git a/src/lib/i18n/locales/en/schedules.ts b/src/lib/i18n/locales/en/schedules.ts new file mode 100644 index 000000000..d7c3423b4 --- /dev/null +++ b/src/lib/i18n/locales/en/schedules.ts @@ -0,0 +1,100 @@ +export const Namespace = 'schedules' as const; + +export const Strings = { + edit: 'Edit Schedule', + create: 'Create Schedule', + editing: 'Editing Schedule...', + creating: 'Creating Schedule...', + 'back-to-schedule': 'Back to Schedule', + 'back-to-schedules': 'Back to Schedules', + id: 'Schedule ID', + schedule: 'Schedule', + frequency: 'Frequency', + 'schedule-spec': 'Schedule Spec', + 'schedule-input': 'Schedule Input', + 'empty-state-title': 'No Schedules Found', + 'empty-state-description': + 'Try adjusting or clearing the filters to see the Schedules running on this Namespace.', + 'error-message-fetching': 'Error fetching schedules', + 'recent-runs': 'Recent Runs', + 'recent-runs-empty-state-title': 'No Recent Runs', + 'upcoming-runs': 'Upcoming Runs', + 'upcoming-runs-empty-state-title': 'No Upcoming Runs', + loading: 'Loading Schedule...', + deleting: 'Deleting Schedule...', + 'delete-schedule-error': 'Cannot delete schedule. {{error}}', + pause: 'Pause', + unpause: 'Unpause', + 'schedule-actions': 'Schedule Actions', + 'pause-modal-title': 'Pause Schedule?', + 'pause-modal-confirmation': 'Are you sure you want to pause {{schedule}}?', + 'pause-reason': 'Enter a reason for pausing the schedule.', + 'unpause-modal-title': 'Unpause Schedule?', + 'unpause-modal-confirmation': + 'Are you sure you want to unpause {{schedule}}?', + 'unpause-reason': 'Enter a reason for unpausing the schedule.', + trigger: 'Trigger', + backfill: 'Backfill', + 'more-options': 'More options', + 'trigger-modal-title': 'Trigger Immediately', + 'trigger-unspecified-title': 'Use Policy', + 'trigger-unspecified-description': "Use the Schedule's overlap policy.", + 'trigger-skip-title': 'Skip', + 'trigger-skip-description': + 'When the workflow completes, the next occurrence that is scheduled after that time is considered.', + 'trigger-buffer-one-title': 'Buffer One', + 'trigger-buffer-one-description': + 'Start the workflow again as soon as the current workflow completes, but buffer only one start. If another start is scheduled to happen while the workflow is running, and a workflow is already buffered, only the first workflow starts after the running workflow completes.', + 'trigger-buffer-all-title': 'Buffer All', + 'trigger-buffer-all-description': + 'Buffer any number of workflow starts to happen sequentially, beginning immediately after the running workflow completes.', + 'trigger-cancel-other-title': 'Cancel Other', + 'trigger-cancel-other-description': + 'If another workflow is running, cancel it. After the previous workflow completes cancellation, start the new workflow.', + 'trigger-terminate-other-title': 'Terminate Other', + 'trigger-terminate-other-description': + 'If another workflow is running, terminate it and start the new workflow immediately.', + 'trigger-allow-all-title': 'Allow All', + 'trigger-allow-all-description': + "Start any number of concurrent workflows. Last completion result and last failure aren't available because the workflows aren't sequential.", + 'delete-modal-title': 'Delete Schedule?', + 'delete-modal-confirmation': 'Are you sure you want to delete {{schedule}}?', + 'advanced-settings': 'Advanced Settings ', + 'start-time': 'Schedule Start Time', + 'end-time': 'Schedule End Time', + jitter: 'Jitter', + 'exclusion-calendar': 'Exclusion Calendar', + 'remaining-actions': 'Remaining Actions', + 'overlap-policy': 'Overlap Policy', + 'recurring-dates-heading': 'Recurring date(s)', + 'recurring-dates-description': + 'Select the specific dates for the schedule to always run on.', + 'recurring-days-heading': 'Recurring day(s)', + 'recurring-days-description': + 'Select the day(s) of the week this schedule will always run on.', + 'time-view-heading': 'Time', + 'time-view-description': + 'Specify the time (UTC) for this schedule to run. By default, the schedule will run at 00:00 UTC if left blank.', + 'interval-view-heading': 'Recurring Time', + 'interval-view-description': + 'Specify the time interval for this schedule to run (for example every 5 minutes).', + 'offset-heading': 'Offset', + 'offset-unit': 'Offset Unit', + 'offset-description': + 'Specify the time to offset when this schedule will run (for example 15 min past the hour).', + 'cron-view-title': 'Cron String', + 'crow-view-example-description': + 'Temporal Workflow Schedule Cron strings follow this format:', + 'cron-view-description': + 'Write or paste in a cron string to generate a schedule.', + 'error-title': 'Error Message', + 'name-label': 'Name', + 'workflow-id-label': 'Workflow Id', + 'workflow-type-label': 'Workflow Type', + 'task-queue-label': 'Task Queue', + 'getting-started-docs-link-preface': 'Go to', + 'getting-started-docs-link': 'docs', + 'getting-started-cli-link-preface': 'or get started with', + 'add-schedule-attr': 'Schedule Attributes', + 'add-workflow-attr': 'Workflow Attributes', +} as const; diff --git a/src/lib/i18n/locales/en/search-attributes.ts b/src/lib/i18n/locales/en/search-attributes.ts new file mode 100644 index 000000000..3f25920c9 --- /dev/null +++ b/src/lib/i18n/locales/en/search-attributes.ts @@ -0,0 +1,44 @@ +export const Namespace = 'search-attributes' as const; + +export const Strings = { + // Component headers and labels + 'column-attribute': 'Attribute', + 'column-type': 'Type', + 'attribute-label': 'Attribute {{index}}', + 'type-label': 'Type for Attribute {{index}}', + 'select-type-placeholder': 'Select type', + + // Buttons + 'add-attribute-button': 'Add New Custom Search Attribute', + 'save-button': 'Save', + 'saving-button': 'Saving...', + 'cancel-button': 'Cancel', + + // Messages + 'validation-error-title': 'Validation Error', + 'save-success': 'Search attributes saved successfully', + 'save-error': 'Failed to save search attributes', + 'save-error-generic': 'An error occurred while saving search attributes', + 'load-error-title': 'Failed to Load Search Attributes', + 'error-title': 'Error', + + // Validation messages + 'validation-name-required': 'Attribute name is required', + 'validation-names-unique': 'Attribute names must be unique', + + // Development messages + 'crud-not-implemented': + 'CRUD operations will be implemented when SDK team adds endpoints', + + // Type labels + 'type-keyword': 'Keyword', + 'type-text': 'Text', + 'type-int': 'Int', + 'type-double': 'Double', + 'type-bool': 'Bool', + 'type-datetime': 'DateTime', + 'type-keywordlist': 'KeywordList', + + // Story titles + 'story-title': 'Custom Search Attributes for {{namespace}}', +} as const; diff --git a/src/lib/i18n/locales/en/typed-errors.ts b/src/lib/i18n/locales/en/typed-errors.ts new file mode 100644 index 000000000..590b12fd7 --- /dev/null +++ b/src/lib/i18n/locales/en/typed-errors.ts @@ -0,0 +1,188 @@ +export const Namespace = 'typed-errors'; + +export const Strings = { + 'link-preface': 'Learn more about ', + Unspecified: { + title: 'Unspecified', + description: 'The Workflow Task failed. See error for details.', + }, + UnhandledCommand: { + title: 'Unhandled Command', + description: + 'The Workflow Task failed because there are new available events since the last Workflow Task started. A retry Workflow Task has been scheduled and the Workflow will have a chance to handle those new events.', + }, + BadScheduleActivityAttributes: { + title: 'Bad Schedule Activity Attributes', + description: + 'The Workflow Task failed because of missing or incorrect ScheduleActivity attributes.', + }, + BadRequestCancelActivityAttributes: { + title: 'Bad Request Cancel Activity Attributes', + description: + 'The Workflow Task failed because of bad RequestCancelActivity attributes. An Activity was scheduled to cancel, but the scheduled event id was never set.', + }, + BadStartTimerAttributes: { + title: 'Bad Start Timer Attributes', + description: + 'The Workflow Task failed because the scheduled event is missing a timer id.', + }, + BadCancelTimerAttributes: { + title: 'Bad Cancel Timer Attributes', + description: + 'The Workflow Task failed when trying to cancel a timer due to an unset timer id.', + }, + BadRecordMarkerAttributes: { + title: 'Bad Record Marker Attributes', + description: + 'The Workflow Task failed because of a missing or invalid Marker name.', + }, + BadCompleteWorkflowExecutionAttributes: { + title: 'Bad Complete Workflow Execution Attributes', + description: + 'The Workflow Task failed because of an unset attribute on CompleteWorkflowExecution.', + }, + BadFailWorkflowExecutionAttributes: { + title: 'Bad Fail Workflow Execution Attributes', + description: + 'The Workflow Task failed because of an unset FailWorkflowExecution attribute or failure.', + }, + BadCancelWorkflowExecutionAttributes: { + title: 'Bad Cancel Workflow Execution Attributes', + description: + 'The Workflow Task failed because of an unset attribute on CancelWorkflowExecution.', + }, + BadRequestCancelExternalAttributes: { + title: 'Bad Request Cancel External Attributes', + description: + 'The Workflow Task failed due to an invalid attribute on a request to cancel an external Workflow. Check the Failure Message for more details.', + }, + BadContinueAsNewAttributes: { + title: 'Bad Continue As New Attributes', + description: + 'The Workflow Task failed because it failed to validate on a ContinueAsNew attribute. Check the Failure Message for more details.', + }, + StartTimerDuplicateId: { + title: 'Start Timer Duplicate ID', + description: + 'The Workflow Task failed because a timer with the given timer id has already started.', + }, + ResetStickyTaskQueue: { + title: 'Reset Sticky Task Queue', + description: + 'The Workflow Task failed because the Sticky Task Queue needs to be reset. The system will automatically retry.', + }, + WorkflowWorkerUnhandledFailure: { + title: 'Workflow Worker Unhandled Failure', + description: + 'The Workflow Task failed due to an unhandled failure from the Workflow code.', + action: 'deterministic constraints', + link: 'https://docs.temporal.io/workflows/#deterministic-constraints', + }, + WorkflowTaskHeartbeatError: { + title: 'Workflow Task Heartbeat Error', + description: + 'The Workflow Task failed to send a heartbeat while executing long-running local Activities. These local Activities will re-execute on the next Workflow Task attempt. If this error is persistent, these local Activities will run repeatedly until the Workflow times out.', + }, + BadSignalWorkflowExecutionAttributes: { + title: 'Bad Signal Workflow Execution Attributes', + description: + 'The Workflow Task failed to validate attributes for SignalWorkflowExecution. Check the Failure Message for more details.', + }, + BadStartChildExecutionAttributes: { + title: 'Bad Start Child Execution Attributes', + description: + 'The Workflow Task failed to validate attributes needed for StartChildWorkflowExecution. Check the Failure Message for more details.', + }, + ForceCloseCommand: { + title: 'Force Close Command', + description: + 'The Workflow Task was forced to close. A retry will be scheduled if the error is recoverable.', + }, + FailoverCloseCommand: { + title: 'Failover Close Command', + description: + 'The Workflow Task was forced to close due to a Namespace failover. A retry will be scheduled automatically.', + }, + BadSignalInputSize: { + title: 'Bad Signal Input Size', + description: + 'The payload has exceeded the available input size on a Signal.', + }, + BadBinary: { + title: 'Bad Binary', + description: + 'The system failed this Workflow Task because the deployment of this Worker is marked as bad binary.', + }, + ScheduleActivityDuplicateId: { + title: 'Schedule Activity Duplicate ID', + description: + 'The Workflow Task failed because the Activity ID is already in use, please check if you have specified the same Activity ID in your workflow.', + }, + BadSearchAttributes: { + title: 'Bad Search Attributes', + description: + 'A Search attribute is either missing or the value exceeds the limit. This might cause Workflow tasks to continue to retry without success.', + action: 'configuring search attributes', + link: 'https://docs.temporal.io/visibility#search-attribute', + }, + NonDeterministicError: { + title: 'Non Deterministic Error', + description: + 'A non-deterministic error has caused the Workflow Task to fail. This usually means the workflow code has a non-backward compatible change without a proper versioning branch.', + }, + BadModifyWorkflowPropertiesAttributes: { + title: 'Bad Modify Workflow Properties Attributes', + description: + 'The Workflow Task failed to validate attributes on ModifyWorkflowProperty on the upsert memo. Check the Failure Message for more details.', + }, + PendingChildWorkflowsLimitExceeded: { + title: 'Pending Child Workflows Limit Exceeded', + description: + 'The capacity for pending child Workflows has been reached. The Workflow Task was failed to prevent any more child Workflows from being added.', + }, + PendingActivitiesLimitExceeded: { + title: 'Pending Activities Limit Exceeded', + description: + 'The capacity for pending Activities has been reached. The Workflow Task was failed to prevent another Activity from being created.', + }, + PendingSignalsLimitExceeded: { + title: 'Pending Signals Limit Exceeded', + description: + 'The capacity for pending Signals to be sent from this Workflow has been reached.', + }, + PendingRequestCancelLimitExceeded: { + title: 'Pending Request Cancel Limit Exceeded', + description: + 'The capacity for pending requests to cancel other Workflows has been reached.', + }, + BadUpdateWorkflowExecutionMessage: { + title: 'Bad Update', + description: + 'A Workflow Execution tried to complete before receiving an Update.', + }, + UnhandledUpdate: { + title: 'Unhandled Update', + description: + 'A Workflow Update was received by the Temporal Server while a Workflow Task was being processed on a Worker.', + }, + BadScheduleNexusOperationAttributes: { + title: 'Bad Schedule Nexus Operation Attributes', + description: + 'A workflow task completed with an invalid ScheduleNexusOperation command.', + }, + PendingNexusOperationsLimitExceeded: { + title: 'Pending Nexus Operations Limit Exceeded', + description: + 'A workflow task completed requesting to schedule a Nexus Operation exceeding the server configured limit.', + }, + BadRequestCancelNexusOperationAttributes: { + title: 'Bad Request Cancel Nexus Operation Attributes', + description: + 'A workflow task completed with an invalid RequestCancelNexusOperation command.', + }, + FeatureDisabled: { + title: 'Feature Disabled', + description: + "A workflow task completed requesting a feature that's disabled on the server (either system wide or - typically - for the workflow's namespace). Check the workflow task failure message for more information.", + }, +} as const; diff --git a/src/lib/i18n/locales/en/workers.ts b/src/lib/i18n/locales/en/workers.ts new file mode 100644 index 000000000..887059633 --- /dev/null +++ b/src/lib/i18n/locales/en/workers.ts @@ -0,0 +1,29 @@ +export const Namespace = 'workers' as const; + +export const Strings = { + workers: 'Workers', + version: 'Version', + versioning: 'Versioning', + retirability: 'Retirability', + buildId: 'Build ID', + 'assignment-rules': 'Assignment Rules', + 'redirect-rules': 'Redirect Rules', + default: 'Default', + overall: 'Overall', + 'compatible-build-ids': 'Compatible Build IDs', + 'version-sets': 'Version Sets', + 'no-version-sets-found': 'No Version Sets found', + 'no-assignment-rules-found': 'No Assignment Rules found', + 'no-redirect-rules-found': 'No Redirect Rules found', + 'show-inactive-assignment-rules': 'Show Inactive Assignment Rules', + 'last-used-version': 'Last used version', + 'next-version': 'Next version', + 'ready-to-be-retired': 'Ready to be Retired', + 'max-version-sets-title': 'Limit reached for Compatible Version Sets', + 'max-version-sets-description': + 'You can increase the number of Compatible Version sets via the limit.versionCompatibleSetsPerQueue dynamic config property.', + 'viewing-pinned-build-ids': + 'Viewing workers for pinned Build ID. Go to the Task Queue page to view all workers.', + 'viewing-auto-upgrade-build-ids': + 'Viewing workers for current and ramping Build IDs. Go to the Task Queue page to view all workers.', +} as const; diff --git a/src/lib/i18n/locales/en/workflows.ts b/src/lib/i18n/locales/en/workflows.ts new file mode 100644 index 000000000..e93ee6525 --- /dev/null +++ b/src/lib/i18n/locales/en/workflows.ts @@ -0,0 +1,295 @@ +export const Namespace = 'workflows' as const; + +export const Strings = { + 'loading-workflows': 'Loading workflows', + 'recent-workflows': 'Recent Workflows', + 'recent-workflows-link': 'View Recent Workflows', + 'workflows-count_one': '{{count, number}} workflow', + 'workflows-count_other': '{{count, number}} workflows', + 'workflows-error-querying': + 'A error has occurred while querying for Workflows.', + 'filtered-workflows-count': + 'Results {{filtered, number}} of {{total, number}} workflows', + terminate: 'Terminate', + 'terminate-latest': 'Terminate Latest Run', + 'batch-terminate-modal-title': 'Terminate Workflows', + 'batch-cancel-modal-title': 'Cancel Workflows', + 'batch-reset-modal-title': 'Reset Workflows', + 'workflow-action-reason-placeholder': '{{action}} from the Web UI', + 'workflow-action-reason-placeholder-with-email': + '{{action}} from the Web UI by {{email}}', + 'batch-operation-confirmation-all': + 'Are you sure you want to {{action}} all workflows matching the following query? This action cannot be undone.', + 'batch-operation-count-disclaimer': + 'Note: The actual count of workflows that will be affected is the total number of running workflows matching this query at the time of clicking "{{action}}".', + 'batch-confirmation_one': + 'Are you sure you want to {{action}} one running workflow?', + 'batch-confirmation_other': + 'Are you sure you want to {{action}} {{count, number}} running workflows?', + 'batch-reset-confirmation_one': + 'Are you sure you want to reset one workflow?', + 'batch-reset-confirmation_other': + 'Are you sure you want to reset {{count, number}} workflows?', + 'batch-operation-confirmation-input-hint': + 'If you supply a custom reason, "{{placeholder}}" will be appended to it. If you omit a reason, the placeholder will be used.', + 'batch-terminate-all-success': + 'The batch terminate request is processing in the background.', + 'batch-cancel-all-success': + 'The batch cancel request is processing in the background.', + 'batch-reset-all-success': + 'The batch reset request is processing in the background.', + 'configure-headers': 'Configure {{title}}', + 'close-configure-headers': 'Close {{title}} configuration', + 'configure-headers-description': + 'Add (<1>), re-arrange (<2>), and remove (<3>), {{type}} to personalize the {{title}} Table.', + 'all-statuses': 'All Statuses', + running: 'Running', + 'timed-out': 'Timed Out', + completed: 'Completed', + failed: 'Failed', + 'contd-as-new': "Cont'd as New", + 'continued-as-new': 'Continued as New', + terminated: 'Terminated', + canceled: 'Canceled', + pause: 'Pause', + unpause: 'Unpause', + paused: 'Paused', + reset: 'Reset', + signal: 'Send a Signal', + update: 'Send an Update', + 'update-success': 'Update successful', + 'update-in-progress': 'Update in progress', + 'n-selected': '{{count, number}} selected', + 'all-selected': 'All {{count, number}} selected.', + 'select-all-leading': 'or ', + 'select-all': 'select all {{count, number}} workflows', + 'select-all-trailing': ' matching your query', + 'request-cancellation': 'Request Cancellation', + 'back-to-workflows': 'Back to Workflows', + input: 'Input', + result: 'Result', + 'initial-input': 'Initial Input', + 'example-input': 'Example Input', + 'input-and-results': 'Input and Results', + 'continued-as-new-with-input': 'Continued as New with Input', + results: 'Results', + 'event-history-view': 'Event History View', + 'event-history': 'Event History', + history: 'History', + 'full-history': 'Full History', + compact: 'Compact', + json: 'JSON', + download: 'Download', + 'workflow-actions': 'Workflow Actions', + 'reset-disabled-unauthorized': + 'Resetting workflows is not enabled, please contact your administrator for assistance.', + 'reset-disabled-pending-children': + 'Cannot reset workflows with pending children.', + 'reset-disabled-no-events': + 'Cannot reset workflows without WorkflowTaskStarted, WorkflowTaskCompleted, or WorkflowTaskTimedOut events.', + 'signal-disabled': + 'Signaling workflows is not enabled, please contact your administrator for assistance.', + 'update-disabled': + 'Updating workflows is not enabled, please contact your administrator for assistance.', + 'terminate-disabled': + 'Terminating workflows is not enabled, please contact your adminstrator for assistance.', + 'terminate-success': 'Workflow terminated.', + 'cancel-success': 'Workflow canceled.', + 'signal-success': 'Workflow signaled.', + 'reset-modal-title': 'Reset Workflow', + 'reset-event-radio-group-description': 'Choose an Event to reset to', + 'reset-reapply-type-label': + 'Reapply Signals that happened after the Reset point', + 'reset-exclude-signals': + 'Exclude Signals that happened after the Reset point.', + 'reset-exclude-updates': + 'Exclude Updates that happened after the Reset point.', + 'cancel-modal-title': 'Cancel Workflow', + 'cancel-modal-confirmation': + 'Are you sure you want to cancel this workflow? This action cannot be undone.', + 'terminate-modal-title': 'Terminate Workflow', + 'terminate-modal-confirmation': + 'Are you sure you want to terminate this workflow? This action cannot be undone.', + 'signal-modal-title': 'Send a Signal', + 'signal-name-label': 'Signal name', + 'signal-payload-input-label': 'Data', + 'signal-payload-input-label-hint': '(only single JSON payload supported)', + 'update-modal-title': 'Send an Update', + 'cancel-request-sent': 'Cancel Request Sent', + 'cancel-request-sent-description': + "The request to cancel this Workflow Execution has been sent. If the Workflow uses the cancellation API, it'll cancel at the next available opportunity.", + 'reset-success-alert-title': 'This Workflow has been reset', + 'reset-success-alert-description': + 'You can find the resulting Workflow Execution <1>here.', + 'history-tab': 'History', + 'workflow-history': 'Workflow History', + 'workers-tab': 'Workers', + 'pending-activities-tab': 'Pending Activities', + 'call-stack-tab': 'Call Stack', + 'queries-tab': 'Queries', + 'metadata-tab': 'Metadata', + 'update-tab': 'Update', + 'workflow-404-title': 'This is not the Workflow you are looking for', + 'workflow-error-title': + 'We are having technical difficulties retrieving this Workflow', + 'workflow-error-no-workers-title': 'No Workers Running', + 'workflow-error-no-workers-description': + 'There are no Workers polling the {{taskQueue}} Task Queue.', + 'workflow-error-no-compatible-workers-title': 'No Compatible Workers Running', + 'workflow-error-no-compatible-workers-description': + 'There are no compatible Workers polling the {{taskQueue}} Task Queue.', + 'state-transitions': 'State Transitions', + 'start-and-close-time': 'Start & Close Time', + relationships: 'Relationships', + parents_zero: '0 Parents', + parents_one: '1 Parent', + 'pending-children_one': '1 Pending Child', + 'pending-children_other': '{{count}} Pending Children', + children_one: '1 Child', + children_other: '{{count}} Children', + 'show-children': 'All Workflows Visible', + 'hide-children': 'Child Workflows Hidden', + first: '{{count}} First', + previous: '{{count}} Previous', + next: '{{count}} Next', + 'no-relationships': "This workflow doesn't have any relationships", + 'parent-id': 'Parent Workflow ID', + 'parent-run-id': 'Parent Run ID', + 'parent-workflow': 'Parent Workflow', + 'first-execution': 'First Execution', + 'previous-execution': 'Previous Execution', + 'current-execution': 'Current Execution', + 'latest-execution': 'Latest Execution', + 'next-execution': 'Next Execution', + 'child-id': 'Child Workflow ID', + 'child-run-id': 'Child Run ID', + 'pending-activities': 'Pending Activities', + 'pending-activities-canceled': 'Pending activities have been canceled.', + 'activity-type': 'Activity Type', + 'last-heartbeat': 'Last Heartbeat', + attempt: 'Attempt', + 'attempts-left': 'Attempts Left', + 'last-attempt-completed-time': 'Last Attempt Completed Time', + retry: 'Retry', + 'next-retry': 'Next Retry', + 'retry-interval': 'Retry Interval', + expiration: 'Expiration', + 'heartbeat-details': 'Heartbeat Details', + 'last-failure': 'Last Failure', + 'last-failure-with-stack-trace': 'Last Failure with Stack Trace', + 'last-accessed': 'Last Accessed', + 'workflow-task-handler': 'Workflow Task Handler', + 'activity-handler': 'Activity Handler', + 'workers-empty-state': 'No Workers Found', + 'call-stack-empty-state': 'No Call Stacks Found', + 'no-workers-failure-message': + 'This will fail if you have no workers running.', + 'no-workers-running-message': + 'Please make sure you have at least one worker running.', + 'call-stack-alert': + 'This is a call stack showing each location where Workflow code is waiting.', + 'call-stack-at': 'Call Stack at', + 'call-stack-link-preface': 'To enable ', + 'call-stack-link': 'call stacks', + 'call-stack-link-postface': ', run a Worker on the {{taskQueue}} Task Queue.', + 'json-formatting': 'JSON Formatting', + 'query-type': 'Query Type', + 'query-arg': 'Query Arg', + 'run-query': 'Run Query', + 'refresh-query': 'Refresh Query', + 'pending-activities-empty-state': 'No Pending Activities', + 'activity-id': 'Activity ID', + summary: 'Summary', + details: 'Details', + 'summary-and-details': 'Summary & Details', + 'current-details': 'Current Details', + 'maximum-attempts': 'Maximum Attempts', + 'retry-expiration': 'Retry Expiration', + state: 'State', + 'last-started-time': 'Last Started Time', + 'scheduled-time': 'Scheduled Time', + 'scheduled-event': 'Scheduled Event', + 'last-worker-identity': 'Last Worker Identity', + unlimited: 'Unlimited', + 'no-expiration': 'No Expiration', + 'no-retry': 'None', + filter: 'Filter', + 'view-search-input': 'View Raw Query', + 'copy-search-input': 'Copy Raw Query', + 'view-search-description': 'For advanced search syntax', + 'close-search-input': 'Show Filters', + 'select-time': 'Select Time', + 'search-placeholder': 'Enter a query', + 'child-workflows': 'Child Workflows', + 'retry-workflows': 'Retry Workflows', + 'workflow-name': 'Workflow Name', + 'filter-by': 'filter by {{workflowName}} type', + 'select-all-workflows': 'Select all Workflows', + 'select-workflow': 'Select Workflow {{workflow}}', + 'empty-state-title': 'No Workflows Found', + 'empty-state-description': + 'If you have filters applied, try adjusting them. Otherwise please check your syntax and try again.', + 'remove-filter-label': 'Remove {{attribute}} filter', + 'remove-keyword-label': 'Remove {{keyword}} keyword', + 'move-column-up-label': 'Move {{column}} column up', + 'move-column-down-label': 'Move {{column}} column down', + 'add-column-label': 'Add {{column}} column', + 'remove-column-label': 'Remove {{column}} column', + 'all-headings-in-view': 'All available columns are in view', + 'no-headings-in-view': 'No columns in view', + 'archived-workflows': 'Archived Workflows', + archival: 'Archival', + 'workflow-query-empty-state-title': 'No Results', + 'workflow-query-empty-state-preface': + 'There are no results for the applied filters.', + 'workflow-query-empty-state-postface': + 'Try adjusting or clearing the filters to see the Workflows running on this Namespace.', + 'workflow-query-error-state': 'There is an error with filtering Workflows.', + 'workflow-empty-state-title': 'No Workflows running in this Namespace', + 'workflow-empty-state-description': + 'You can populate the Web UI with sample Workflows. You can find a complete list of executable code samples at', + 'visibility-disabled-archival': + 'This namespace is currently enabled for archival but visibility is not enabled.', + 'archival-link-preface': 'To enable ', + 'archival-link': 'archival visibility', + 'archival-disabled-title': + 'This namespace is currently not enabled for archival.', + 'archival-disabled-details': + 'Run this command to enable archival visibility for event histories', + 'archival-empty-state-description': + 'No results found for archival visibility.', + 'basic-search': 'Basic Search', + 'advanced-search': 'Advanced Search', + 'time-range': 'Time Range', + 'pending-activities-link': 'Show all Pending Activities', + 'duration-filter-placeholder': + 'e.g. "2h45m", "hh:mm:ss", or "1000" nanoseconds', + 'start-workflow': 'Start Workflow', + 'start-workflow-like-this-one': 'Start Workflow Like This One', + 'custom-search-attribute': 'Custom Search Attribute', + 'select-attribute': 'Select Attribute', + 'unsupported-attribute': 'Unsupported attribute type', + 'add-search-attribute': 'Add a Search Attribute', + 'pending-workflow-task': 'Pending Workflow Task', + 'original-scheduled-time': 'Original Scheduled Time', + 'started-time': 'Started Time', + 'start-workflow-success': 'Workflow started successfully', + 'start-workflow-error': 'Error starting Workflow', + encoding: 'Encoding', + 'message-type': 'Message Type', + 'user-metadata': 'User Metadata', + 'markdown-supported': 'Markdown Supported', + 'markdown-description': + 'Markdown is supported in the summary and details fields. You can use {namespace}, {workflowId} or {runId} syntax in link href to create dynamic links based on the page you are on. Images are not allowed.', + 'update-id': 'Update ID (optional)', + 'update-name-label': 'Update name', + 'no-current-details': 'No Current Details', + 'update-details': 'Update Details', + 'billable-actions': 'Billable Actions (estimate)', + 'estimated-billable-actions': 'Estimated Billable Actions', + 'billable-actions-disclaimer': + 'This is an estimated count and does not capture all Billable Actions. View documentation for more details.', + 'pending-nexus-operation': 'Pending Nexus Operation', + 'schedule-event-id': 'Scheduled Event ID', + 'schedule-to-close-timeout': 'Schedule to Close Timeout', +} as const; diff --git a/src/lib/i18n/locales/index.ts b/src/lib/i18n/locales/index.ts new file mode 100644 index 000000000..f30cdaa14 --- /dev/null +++ b/src/lib/i18n/locales/index.ts @@ -0,0 +1,5 @@ +import { EN, English } from './en'; + +export default { + [EN]: English, +}; diff --git a/src/lib/i18n/translate.svelte b/src/lib/i18n/translate.svelte new file mode 100644 index 000000000..819425c4c --- /dev/null +++ b/src/lib/i18n/translate.svelte @@ -0,0 +1,22 @@ + + +{#if translated !== key} + {translated} +{:else} + +{/if} diff --git a/src/lib/i18n/translate.test.ts b/src/lib/i18n/translate.test.ts new file mode 100644 index 000000000..823596c55 --- /dev/null +++ b/src/lib/i18n/translate.test.ts @@ -0,0 +1,63 @@ +import * as i18next from 'i18next'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + +import { translate } from './translate'; + +describe('translate', () => { + beforeEach(() => { + vi.mock('i18next', async (importOriginal) => { + const actual = (await importOriginal()) as i18next.i18n; + const mockT = vi.fn(); + return { + default: { + ...actual, + t: mockT, + }, + t: mockT, + }; + }); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + test('accepts a namespace and key', () => { + translate('common.loading'); + + expect(i18next.t).toHaveBeenCalledWith('common:loading', {}); + }); + + test('accepts a namespace, key, and replace params', () => { + translate('common.loading', { foo: 'bar' }); + + expect(i18next.t).toHaveBeenCalledWith('common:loading', { + foo: 'bar', + }); + }); + + test('accepts a namespace, key, and count', () => { + translate('common.loading', { count: 10 }); + + expect(i18next.t).toHaveBeenCalledWith('common:loading', { + count: 10, + }); + }); + + test('accepts namespace, key, and a count of 0', () => { + translate('common.loading', { count: 0 }); + + expect(i18next.t).toHaveBeenCalledWith('common:loading', { + count: 0, + }); + }); + + test('accepts a namespace, key, count, and replace parameters', () => { + translate('common.loading', { count: 10, foo: 'bar' }); + + expect(i18next.t).toHaveBeenCalledWith('common:loading', { + foo: 'bar', + count: 10, + }); + }); +}); diff --git a/src/lib/i18n/translate.ts b/src/lib/i18n/translate.ts new file mode 100644 index 000000000..2a9f87b05 --- /dev/null +++ b/src/lib/i18n/translate.ts @@ -0,0 +1,21 @@ +import { t } from 'i18next'; + +import type { I18nKey, I18nReplace, I18nResources } from '.'; + +const translateGeneric = ( + key: I18nKey, + replace: I18nReplace = {}, +): string => { + const [namespace, ...keys] = key.split('.'); + + if (namespace && keys.length > 0) { + const k = keys.join('.'); + return t(`${namespace}:${k}`, replace); + } +}; + +export const createTranslate = () => { + return translateGeneric; +}; + +export const translate = createTranslate(); diff --git a/src/lib/layouts/workflow-header.svelte b/src/lib/layouts/workflow-header.svelte index d3e037ca9..f7e7a8361 100644 --- a/src/lib/layouts/workflow-header.svelte +++ b/src/lib/layouts/workflow-header.svelte @@ -1,199 +1,270 @@ -
-
- +
+
+
-

- - {workflow.id} -

- {#if isRunning} -
- - + +
+
- {/if} -
- {#if cancelInProgress} -
- - The request to cancel this Workflow Execution has been sent. If the - Workflow uses the cancellation API, it'll cancel at the next available - opportunity. -
- {/if} -
+
+ +
+
+ + + + + {#if cancelInProgress} +
+ + {translate('workflows.cancel-request-sent-description')} + +
+ {/if} + {#if workflowHasBeenReset} +
+ + You can find the resulting Workflow Execution here. + +
+ {/if} + + + > + + {workflow?.historyEvents} + + + + {workflowRelationships.relationshipCount} + + + > + + {getWorkflowPollersWithVersions(workflow, workers)?.pollers?.length || + 0} + + + > + +
+ {#if activitiesCanceled} + {/if} + {workflow?.pendingActivities?.length} +
+
+
- - + +
+
- - diff --git a/src/lib/layouts/workflow-history-layout.svelte b/src/lib/layouts/workflow-history-layout.svelte index dcafaf480..ea6e9e7a9 100644 --- a/src/lib/layouts/workflow-history-layout.svelte +++ b/src/lib/layouts/workflow-history-layout.svelte @@ -1,172 +1,160 @@ -
-
- - -
- + +
+ + {#if workflowTaskFailedError} + - -
- - {#if workflow?.parent} -
- - -
{/if} - {#each workflow?.pendingChildren as child (child.runId)} -
- - -
- {/each} - -
- - - -
- -
- - -
-
-
- -
-
+
+
+

+ {translate('workflows.event-history')} +

+
+ + {#if $eventViewType !== 'json'} - exportHistory({ - namespace, - workflowId: workflow.id, - runId: workflow.runId, - })}>Download{reverseSort ? 'Descending' : 'Ascending'} - -
- - - - + {/if} + ($minimizeEventView = !$minimizeEventView)} + >{$minimizeEventView ? 'Minimized' : 'Expanded'} + + ($pauseLiveUpdates = !$pauseLiveUpdates)} + > + {$pauseLiveUpdates ? 'Unfreeze' : 'Freeze'} + + (showDownloadPrompt = true)} + > + {translate('common.download')} + + +
+
+
+ + +
+
+ + + diff --git a/src/lib/layouts/workflow-padded-layout.svelte b/src/lib/layouts/workflow-padded-layout.svelte new file mode 100644 index 000000000..39b6bd689 --- /dev/null +++ b/src/lib/layouts/workflow-padded-layout.svelte @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/lib/layouts/workflow-run-layout.svelte b/src/lib/layouts/workflow-run-layout.svelte index e97e6e500..b9173327b 100644 --- a/src/lib/layouts/workflow-run-layout.svelte +++ b/src/lib/layouts/workflow-run-layout.svelte @@ -1,37 +1,224 @@ -
- {#if $loading} - - {:else} - -
+{#if showJson} +
+ + {stringifyWithBigInt(fullJson, null, 2)} +
+{:else} +
+ {#if workflowError} + + {:else if !$workflowRun.workflow} + + {:else} +
+ +
- - {/if} -
+ {/if} + +{/if} diff --git a/src/lib/models/__snapshots__/workflow-execution.test.ts.snap b/src/lib/models/__snapshots__/workflow-execution.test.ts.snap index a1e009a50..10400a182 100644 --- a/src/lib/models/__snapshots__/workflow-execution.test.ts.snap +++ b/src/lib/models/__snapshots__/workflow-execution.test.ts.snap @@ -1,13 +1,22 @@ -// Vitest Snapshot v1 +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`toWorkflowExecution > should match the snapshot for Canceled workflows 1`] = ` { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": "20s", + "details": undefined, "endTime": "2022-07-01T20:20:50.181506253Z", + "executionTime": "2022-07-01T20:20:50.157120545Z", "historyEvents": "13", + "historySizeBytes": undefined, "id": "temporal.fixture.cancelled.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.cancellation", "parent": { "runId": "b142fc85-8e13-42e5-a824-0f2fafdeabaf", @@ -16,23 +25,49 @@ exports[`toWorkflowExecution > should match the snapshot for Canceled workflows "parentNamespaceId": "c3e6ec56-8059-4bdf-99ac-d50d3fa1616c", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "346e7a24-c660-4f91-a777-ffe3274d8144", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "childWorkflowValue", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:20:50.157120545Z", "stateTransitionCount": "6", "status": "Canceled", + "summary": undefined, "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.cancelled.workflow.id/346e7a24-c660-4f91-a777-ffe3274d8144", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, } `; exports[`toWorkflowExecution > should match the snapshot for Completed workflows 1`] = ` { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": "20s", + "details": undefined, "endTime": "2022-07-01T20:28:52.916373379Z", + "executionTime": "2022-07-01T20:28:48.796369169Z", "historyEvents": "18", + "historySizeBytes": undefined, "id": "temporal.fixture.completed.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.completion", "parent": { "runId": "971e2165-c4f8-4f78-87ca-b652a06eb234", @@ -41,45 +76,93 @@ exports[`toWorkflowExecution > should match the snapshot for Completed workflows "parentNamespaceId": "c3e6ec56-8059-4bdf-99ac-d50d3fa1616c", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "202dcff6-7f35-4c65-995c-bcadce524fb1", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "childWorkflowValue", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:28:48.796369169Z", "stateTransitionCount": "11", "status": "Completed", + "summary": undefined, "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.completed.workflow.id/202dcff6-7f35-4c65-995c-bcadce524fb1", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, } `; exports[`toWorkflowExecution > should match the snapshot for Failed workflows 1`] = ` { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": "20s", + "details": undefined, "endTime": "2022-07-01T20:22:50.027441003Z", + "executionTime": "2022-07-01T20:22:50.015107253Z", "historyEvents": "7", + "historySizeBytes": undefined, "id": "temporal.fixture.failed.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.retry-workflow.ext", "parent": null, "parentNamespaceId": "", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "7e00b341-c579-4bb5-9e28-810d34ef4329", + "searchAttributes": { + "indexedFields": { + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:22:49.015107253Z", "stateTransitionCount": "4", "status": "Failed", + "summary": undefined, "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.failed.workflow.id/7e00b341-c579-4bb5-9e28-810d34ef4329", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, } `; exports[`toWorkflowExecution > should match the snapshot for Running workflows 1`] = ` { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": true, "defaultWorkflowTaskTimeout": "20s", - "endTime": "null", + "details": undefined, + "endTime": null, + "executionTime": "2022-07-01T20:23:49.104303753Z", "historyEvents": "13", + "historySizeBytes": undefined, "id": "temporal.fixture.running.workflow.id", "isRunning": true, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.advanced-visibility", "parent": { "runId": "8f00d989-6bc2-4826-b9f9-7c18ed90c9cc", @@ -114,23 +197,49 @@ exports[`toWorkflowExecution > should match the snapshot for Running workflows 1 }, ], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "2a7ba421-f74b-4b8b-b9d8-e6e30e4caac7", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "canaryTest", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:23:49.104303753Z", "stateTransitionCount": "21", "status": "Running", + "summary": undefined, "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.running.workflow.id/2a7ba421-f74b-4b8b-b9d8-e6e30e4caac7", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, } `; exports[`toWorkflowExecution > should match the snapshot for Terminated workflows 1`] = ` { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": "20s", + "details": undefined, "endTime": "2022-07-01T20:14:51.002030504Z", + "executionTime": "2022-07-01T20:14:48.911627544Z", "historyEvents": "30", + "historySizeBytes": undefined, "id": "temporal.fixture.completed.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.reset.base", "parent": { "runId": "3cbbf515-36da-43b9-a1f3-18a7ec031ddd", @@ -170,23 +279,49 @@ exports[`toWorkflowExecution > should match the snapshot for Terminated workflow }, ], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "16c5fbe5-f82d-43c1-acbc-18c65c449ace", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "childWorkflowValue", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:14:48.911627544Z", "stateTransitionCount": "21", "status": "Terminated", + "summary": undefined, "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.completed.workflow.id/16c5fbe5-f82d-43c1-acbc-18c65c449ace", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, } `; exports[`toWorkflowExecution > should match the snapshot for TimedOut workflows 1`] = ` { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": "20s", + "details": undefined, "endTime": "2022-07-01T14:16:18.804308503Z", + "executionTime": "2022-07-01T13:56:18.803016378Z", "historyEvents": "18", + "historySizeBytes": undefined, "id": "temporal.fixture.timed-out.workflow.id/workflow.advanced-visibility", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.advanced-visibility", "parent": { "runId": "78cba607-189e-41fe-a31b-a2b6061babaa", @@ -195,149 +330,350 @@ exports[`toWorkflowExecution > should match the snapshot for TimedOut workflows "parentNamespaceId": "c3e6ec56-8059-4bdf-99ac-d50d3fa1616c", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": { + "attempt": 1, + "originalScheduledTime": "2022-07-01T14:00:34.963148219Z", + "scheduledTime": "2022-07-01T14:00:34.963148844Z", + "startedTime": null, + "state": "Scheduled", + }, + "rootExecution": undefined, "runId": "445a17cb-6cb4-4508-bc59-187d08c6c6e2", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "canaryTest", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T13:56:18.803016378Z", "stateTransitionCount": "16", "status": "TimedOut", + "summary": undefined, "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.timed-out.workflow.id/workflow.advanced-visibility/445a17cb-6cb4-4508-bc59-187d08c6c6e2", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, } `; exports[`toWorkflowExecutions > should match the snapshot 1`] = ` [ { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": undefined, + "details": undefined, "endTime": "2022-07-01T20:20:50.181506253Z", + "executionTime": "2022-07-01T20:20:50.157120545Z", "historyEvents": "13", + "historySizeBytes": undefined, "id": "temporal.fixture.cancelled.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.cancellation", "parent": null, "parentNamespaceId": "", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "346e7a24-c660-4f91-a777-ffe3274d8144", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "childWorkflowValue", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:20:50.157120545Z", "stateTransitionCount": "6", "status": "Canceled", - "taskQueue": undefined, + "summary": undefined, + "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.cancelled.workflow.id/346e7a24-c660-4f91-a777-ffe3274d8144", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, }, { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": undefined, + "details": undefined, "endTime": "2022-07-01T20:28:52.916373379Z", + "executionTime": "2022-07-01T20:28:48.796369169Z", "historyEvents": "18", + "historySizeBytes": undefined, "id": "temporal.fixture.completed.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.completion", "parent": null, "parentNamespaceId": "", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "202dcff6-7f35-4c65-995c-bcadce524fb1", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "childWorkflowValue", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:28:48.796369169Z", "stateTransitionCount": "11", "status": "Completed", - "taskQueue": undefined, + "summary": undefined, + "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.completed.workflow.id/202dcff6-7f35-4c65-995c-bcadce524fb1", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, }, { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": undefined, + "details": undefined, "endTime": "2022-07-01T20:22:50.027441003Z", + "executionTime": "2022-07-01T20:22:50.015107253Z", "historyEvents": "7", + "historySizeBytes": undefined, "id": "temporal.fixture.failed.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.retry-workflow.ext", "parent": null, "parentNamespaceId": "", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "7e00b341-c579-4bb5-9e28-810d34ef4329", + "searchAttributes": { + "indexedFields": { + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:22:49.015107253Z", "stateTransitionCount": "4", "status": "Failed", - "taskQueue": undefined, + "summary": undefined, + "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.failed.workflow.id/7e00b341-c579-4bb5-9e28-810d34ef4329", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, }, { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": true, "defaultWorkflowTaskTimeout": undefined, - "endTime": "null", + "details": undefined, + "endTime": null, + "executionTime": "2022-07-01T20:23:49.104303753Z", "historyEvents": "19", + "historySizeBytes": undefined, "id": "temporal.fixture.running.workflow.id", "isRunning": true, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.advanced-visibility", "parent": null, "parentNamespaceId": "", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "2a7ba421-f74b-4b8b-b9d8-e6e30e4caac7", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "canaryTest", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:23:49.104303753Z", "stateTransitionCount": "27", "status": "Running", - "taskQueue": undefined, + "summary": undefined, + "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.running.workflow.id/2a7ba421-f74b-4b8b-b9d8-e6e30e4caac7", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, }, { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": undefined, + "details": undefined, "endTime": "2022-07-01T20:14:51.073853795Z", + "executionTime": "2022-07-01T20:14:51.002662504Z", "historyEvents": "42", + "historySizeBytes": undefined, "id": "temporal.fixture.completed.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.reset.base", "parent": null, "parentNamespaceId": "", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "dfd4039d-4bc0-4864-9a24-85d136814977", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "childWorkflowValue", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:14:51.002662504Z", "stateTransitionCount": "13", "status": "Completed", - "taskQueue": undefined, + "summary": undefined, + "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.completed.workflow.id/dfd4039d-4bc0-4864-9a24-85d136814977", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, }, { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": undefined, + "details": undefined, "endTime": "2022-07-01T20:14:51.002030504Z", + "executionTime": "2022-07-01T20:14:48.911627544Z", "historyEvents": "30", + "historySizeBytes": undefined, "id": "temporal.fixture.terminated.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.reset.base", "parent": null, "parentNamespaceId": "", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "16c5fbe5-f82d-43c1-acbc-18c65c449ace", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "childWorkflowValue", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:14:48.911627544Z", "stateTransitionCount": "21", "status": "Terminated", - "taskQueue": undefined, + "summary": undefined, + "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.terminated.workflow.id/16c5fbe5-f82d-43c1-acbc-18c65c449ace", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, }, { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": undefined, + "details": undefined, "endTime": "2022-07-01T14:16:18.804308503Z", + "executionTime": "2022-07-01T13:56:18.803016378Z", "historyEvents": "18", + "historySizeBytes": undefined, "id": "temporal.fixture.timed-out.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.advanced-visibility", "parent": null, "parentNamespaceId": "", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "445a17cb-6cb4-4508-bc59-187d08c6c6e2", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "canaryTest", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T13:56:18.803016378Z", "stateTransitionCount": "16", "status": "TimedOut", - "taskQueue": undefined, + "summary": undefined, + "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.timed-out.workflow.id/445a17cb-6cb4-4508-bc59-187d08c6c6e2", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, }, ] `; diff --git a/src/lib/models/__snapshots__/workflow-execution.write-disabled.test.ts.snap b/src/lib/models/__snapshots__/workflow-execution.write-disabled.test.ts.snap index afc65576f..d7361f40d 100644 --- a/src/lib/models/__snapshots__/workflow-execution.write-disabled.test.ts.snap +++ b/src/lib/models/__snapshots__/workflow-execution.write-disabled.test.ts.snap @@ -1,13 +1,22 @@ -// Vitest Snapshot v1 +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`toWorkflowExecution > should match the snapshot for Canceled workflows 1`] = ` { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": "20s", + "details": undefined, "endTime": "2022-07-01T20:20:50.181506253Z", + "executionTime": "2022-07-01T20:20:50.157120545Z", "historyEvents": "13", + "historySizeBytes": undefined, "id": "temporal.fixture.cancelled.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.cancellation", "parent": { "runId": "b142fc85-8e13-42e5-a824-0f2fafdeabaf", @@ -16,23 +25,49 @@ exports[`toWorkflowExecution > should match the snapshot for Canceled workflows "parentNamespaceId": "c3e6ec56-8059-4bdf-99ac-d50d3fa1616c", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "346e7a24-c660-4f91-a777-ffe3274d8144", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "childWorkflowValue", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:20:50.157120545Z", "stateTransitionCount": "6", "status": "Canceled", + "summary": undefined, "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.cancelled.workflow.id/346e7a24-c660-4f91-a777-ffe3274d8144", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, } `; exports[`toWorkflowExecution > should match the snapshot for Completed workflows 1`] = ` { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": "20s", + "details": undefined, "endTime": "2022-07-01T20:28:52.916373379Z", + "executionTime": "2022-07-01T20:28:48.796369169Z", "historyEvents": "18", + "historySizeBytes": undefined, "id": "temporal.fixture.completed.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.completion", "parent": { "runId": "971e2165-c4f8-4f78-87ca-b652a06eb234", @@ -41,45 +76,93 @@ exports[`toWorkflowExecution > should match the snapshot for Completed workflows "parentNamespaceId": "c3e6ec56-8059-4bdf-99ac-d50d3fa1616c", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "202dcff6-7f35-4c65-995c-bcadce524fb1", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "childWorkflowValue", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:28:48.796369169Z", "stateTransitionCount": "11", "status": "Completed", + "summary": undefined, "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.completed.workflow.id/202dcff6-7f35-4c65-995c-bcadce524fb1", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, } `; exports[`toWorkflowExecution > should match the snapshot for Failed workflows 1`] = ` { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": "20s", + "details": undefined, "endTime": "2022-07-01T20:22:50.027441003Z", + "executionTime": "2022-07-01T20:22:50.015107253Z", "historyEvents": "7", + "historySizeBytes": undefined, "id": "temporal.fixture.failed.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.retry-workflow.ext", "parent": null, "parentNamespaceId": "", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "7e00b341-c579-4bb5-9e28-810d34ef4329", + "searchAttributes": { + "indexedFields": { + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:22:49.015107253Z", "stateTransitionCount": "4", "status": "Failed", + "summary": undefined, "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.failed.workflow.id/7e00b341-c579-4bb5-9e28-810d34ef4329", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, } `; exports[`toWorkflowExecution > should match the snapshot for Running workflows 1`] = ` { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": "20s", - "endTime": "null", + "details": undefined, + "endTime": null, + "executionTime": "2022-07-01T20:23:49.104303753Z", "historyEvents": "13", + "historySizeBytes": undefined, "id": "temporal.fixture.running.workflow.id", "isRunning": true, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.advanced-visibility", "parent": { "runId": "8f00d989-6bc2-4826-b9f9-7c18ed90c9cc", @@ -114,23 +197,49 @@ exports[`toWorkflowExecution > should match the snapshot for Running workflows 1 }, ], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "2a7ba421-f74b-4b8b-b9d8-e6e30e4caac7", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "canaryTest", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:23:49.104303753Z", "stateTransitionCount": "21", "status": "Running", + "summary": undefined, "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.running.workflow.id/2a7ba421-f74b-4b8b-b9d8-e6e30e4caac7", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, } `; exports[`toWorkflowExecution > should match the snapshot for Terminated workflows 1`] = ` { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": "20s", + "details": undefined, "endTime": "2022-07-01T20:14:51.002030504Z", + "executionTime": "2022-07-01T20:14:48.911627544Z", "historyEvents": "30", + "historySizeBytes": undefined, "id": "temporal.fixture.completed.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.reset.base", "parent": { "runId": "3cbbf515-36da-43b9-a1f3-18a7ec031ddd", @@ -170,23 +279,49 @@ exports[`toWorkflowExecution > should match the snapshot for Terminated workflow }, ], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": null, + "rootExecution": undefined, "runId": "16c5fbe5-f82d-43c1-acbc-18c65c449ace", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "childWorkflowValue", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:14:48.911627544Z", "stateTransitionCount": "21", "status": "Terminated", + "summary": undefined, "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.completed.workflow.id/16c5fbe5-f82d-43c1-acbc-18c65c449ace", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, } `; exports[`toWorkflowExecution > should match the snapshot for TimedOut workflows 1`] = ` { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": "20s", + "details": undefined, "endTime": "2022-07-01T14:16:18.804308503Z", + "executionTime": "2022-07-01T13:56:18.803016378Z", "historyEvents": "18", + "historySizeBytes": undefined, "id": "temporal.fixture.timed-out.workflow.id/workflow.advanced-visibility", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.advanced-visibility", "parent": { "runId": "78cba607-189e-41fe-a31b-a2b6061babaa", @@ -195,149 +330,350 @@ exports[`toWorkflowExecution > should match the snapshot for TimedOut workflows "parentNamespaceId": "c3e6ec56-8059-4bdf-99ac-d50d3fa1616c", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": { + "attempt": 1, + "originalScheduledTime": "2022-07-01T14:00:34.963148219Z", + "scheduledTime": "2022-07-01T14:00:34.963148844Z", + "startedTime": null, + "state": "Scheduled", + }, + "rootExecution": undefined, "runId": "445a17cb-6cb4-4508-bc59-187d08c6c6e2", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "canaryTest", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T13:56:18.803016378Z", "stateTransitionCount": "16", "status": "TimedOut", + "summary": undefined, "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.timed-out.workflow.id/workflow.advanced-visibility/445a17cb-6cb4-4508-bc59-187d08c6c6e2", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, } `; exports[`toWorkflowExecutions > should match the snapshot 1`] = ` [ { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": undefined, + "details": undefined, "endTime": "2022-07-01T20:20:50.181506253Z", + "executionTime": "2022-07-01T20:20:50.157120545Z", "historyEvents": "13", + "historySizeBytes": undefined, "id": "temporal.fixture.cancelled.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.cancellation", "parent": null, "parentNamespaceId": "", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "346e7a24-c660-4f91-a777-ffe3274d8144", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "childWorkflowValue", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:20:50.157120545Z", "stateTransitionCount": "6", "status": "Canceled", - "taskQueue": undefined, + "summary": undefined, + "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.cancelled.workflow.id/346e7a24-c660-4f91-a777-ffe3274d8144", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, }, { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": undefined, + "details": undefined, "endTime": "2022-07-01T20:28:52.916373379Z", + "executionTime": "2022-07-01T20:28:48.796369169Z", "historyEvents": "18", + "historySizeBytes": undefined, "id": "temporal.fixture.completed.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.completion", "parent": null, "parentNamespaceId": "", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "202dcff6-7f35-4c65-995c-bcadce524fb1", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "childWorkflowValue", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:28:48.796369169Z", "stateTransitionCount": "11", "status": "Completed", - "taskQueue": undefined, + "summary": undefined, + "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.completed.workflow.id/202dcff6-7f35-4c65-995c-bcadce524fb1", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, }, { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": undefined, + "details": undefined, "endTime": "2022-07-01T20:22:50.027441003Z", + "executionTime": "2022-07-01T20:22:50.015107253Z", "historyEvents": "7", + "historySizeBytes": undefined, "id": "temporal.fixture.failed.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.retry-workflow.ext", "parent": null, "parentNamespaceId": "", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "7e00b341-c579-4bb5-9e28-810d34ef4329", + "searchAttributes": { + "indexedFields": { + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:22:49.015107253Z", "stateTransitionCount": "4", "status": "Failed", - "taskQueue": undefined, + "summary": undefined, + "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.failed.workflow.id/7e00b341-c579-4bb5-9e28-810d34ef4329", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, }, { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": undefined, - "endTime": "null", + "details": undefined, + "endTime": null, + "executionTime": "2022-07-01T20:23:49.104303753Z", "historyEvents": "19", + "historySizeBytes": undefined, "id": "temporal.fixture.running.workflow.id", "isRunning": true, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.advanced-visibility", "parent": null, "parentNamespaceId": "", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "2a7ba421-f74b-4b8b-b9d8-e6e30e4caac7", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "canaryTest", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:23:49.104303753Z", "stateTransitionCount": "27", "status": "Running", - "taskQueue": undefined, + "summary": undefined, + "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.running.workflow.id/2a7ba421-f74b-4b8b-b9d8-e6e30e4caac7", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, }, { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": undefined, + "details": undefined, "endTime": "2022-07-01T20:14:51.073853795Z", + "executionTime": "2022-07-01T20:14:51.002662504Z", "historyEvents": "42", + "historySizeBytes": undefined, "id": "temporal.fixture.completed.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.reset.base", "parent": null, "parentNamespaceId": "", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "dfd4039d-4bc0-4864-9a24-85d136814977", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "childWorkflowValue", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:14:51.002662504Z", "stateTransitionCount": "13", "status": "Completed", - "taskQueue": undefined, + "summary": undefined, + "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.completed.workflow.id/dfd4039d-4bc0-4864-9a24-85d136814977", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, }, { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": undefined, + "details": undefined, "endTime": "2022-07-01T20:14:51.002030504Z", + "executionTime": "2022-07-01T20:14:48.911627544Z", "historyEvents": "30", + "historySizeBytes": undefined, "id": "temporal.fixture.terminated.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.reset.base", "parent": null, "parentNamespaceId": "", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "16c5fbe5-f82d-43c1-acbc-18c65c449ace", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "childWorkflowValue", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T20:14:48.911627544Z", "stateTransitionCount": "21", "status": "Terminated", - "taskQueue": undefined, + "summary": undefined, + "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.terminated.workflow.id/16c5fbe5-f82d-43c1-acbc-18c65c449ace", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, }, { + "assignedBuildId": undefined, + "callbacks": [], "canBeTerminated": false, "defaultWorkflowTaskTimeout": undefined, + "details": undefined, "endTime": "2022-07-01T14:16:18.804308503Z", + "executionTime": "2022-07-01T13:56:18.803016378Z", "historyEvents": "18", + "historySizeBytes": undefined, "id": "temporal.fixture.timed-out.workflow.id", "isRunning": false, + "memo": { + "fields": {}, + }, + "mostRecentWorkerVersionStamp": undefined, "name": "workflow.advanced-visibility", "parent": null, "parentNamespaceId": "", "pendingActivities": [], "pendingChildren": [], + "pendingNexusOperations": [], + "pendingWorkflowTask": undefined, + "rootExecution": undefined, "runId": "445a17cb-6cb4-4508-bc59-187d08c6c6e2", + "searchAttributes": { + "indexedFields": { + "BinaryChecksums": [ + "e56c0141e58df0bd405138565d0526f9", + ], + "CustomKeywordField": "canaryTest", + "TemporalChangeVersion": [ + "initial version-3", + ], + }, + }, "startTime": "2022-07-01T13:56:18.803016378Z", "stateTransitionCount": "16", "status": "TimedOut", - "taskQueue": undefined, + "summary": undefined, + "taskQueue": "canary-task-queue", "url": "/workflows/temporal.fixture.timed-out.workflow.id/445a17cb-6cb4-4508-bc59-187d08c6c6e2", + "versioningInfo": undefined, + "workflowExtendedInfo": {}, }, ] `; diff --git a/src/lib/models/banner-state.ts b/src/lib/models/banner-state.ts deleted file mode 100644 index 96fab3042..000000000 --- a/src/lib/models/banner-state.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum BannersState { - TemporalVersion = 1, - UIVersion = 2, -} diff --git a/src/lib/models/core-user.ts b/src/lib/models/core-user.ts index 90c74cf44..5f9366d2b 100644 --- a/src/lib/models/core-user.ts +++ b/src/lib/models/core-user.ts @@ -2,6 +2,7 @@ // Continue to add to Interface as more actions are added in core export interface CoreUser { namespaceWriteDisabled: (namespace: string) => boolean; + isActivityCommandsDisabled: boolean; } // Set context with this key diff --git a/src/lib/models/event-groups/create-event-group.test.ts b/src/lib/models/event-groups/create-event-group.test.ts index fc6d8699c..3e52af0a7 100644 --- a/src/lib/models/event-groups/create-event-group.test.ts +++ b/src/lib/models/event-groups/create-event-group.test.ts @@ -1,4 +1,11 @@ import { describe, expect, it } from 'vitest'; + +import type { + ActivityTaskCompletedEvent, + ActivityTaskScheduledEvent, + WorkflowEvent, +} from '$lib/types/events'; + import { createEventGroup } from './create-event-group'; const scheduledEvent = { diff --git a/src/lib/models/event-groups/create-event-group.ts b/src/lib/models/event-groups/create-event-group.ts index 05629dac3..76bffab75 100644 --- a/src/lib/models/event-groups/create-event-group.ts +++ b/src/lib/models/event-groups/create-event-group.ts @@ -1,20 +1,41 @@ +import type { Payload } from '$lib/types'; +import type { + ActivityTaskScheduledEvent, + CommonHistoryEvent, + MarkerRecordedEvent, + NexusOperationScheduledEvent, + SignalExternalWorkflowExecutionInitiatedEvent, + StartChildWorkflowExecutionInitiatedEvent, + TimerStartedEvent, + WorkflowExecutionSignaledEvent, + WorkflowExecutionUpdateAcceptedEvent, + WorkflowTaskScheduledEvent, +} from '$lib/types/events'; import { isActivityTaskScheduledEvent, - isMarkerRecordedEvent, isLocalActivityMarkerEvent, + isMarkerRecordedEvent, + isNexusOperationScheduledEvent, isSignalExternalWorkflowExecutionInitiatedEvent, isStartChildWorkflowExecutionInitiatedEvent, - isWorkflowExecutionSignaledEvent, isTimerStartedEvent, + isWorkflowExecutionSignaledEvent, + isWorkflowExecutionUpdateAcceptedEvent, + isWorkflowTaskScheduledEvent, } from '$lib/utilities/is-event-type'; + +import type { EventGroup } from './event-groups'; import { - eventIsFailureOrTimedOut, eventIsCanceled, + eventIsFailureOrTimedOut, eventIsTerminated, } from './get-event-in-group'; - import { getGroupId } from './get-group-id'; -import { getEventGroupName } from './get-group-name'; +import { + getEventGroupDisplayName, + getEventGroupLabel, + getEventGroupName, +} from './get-group-name'; import { getLastEvent } from './get-last-event'; type StartingEvents = { @@ -25,32 +46,42 @@ type StartingEvents = { SignalReceived: WorkflowExecutionSignaledEvent; LocalActivity: MarkerRecordedEvent; Marker: MarkerRecordedEvent; + Update: WorkflowExecutionUpdateAcceptedEvent; + WorkflowTask: WorkflowTaskScheduledEvent; + Nexus: NexusOperationScheduledEvent; }; const createGroupFor = ( - event: StartingEvents[K], + event: StartingEvents[K] & { userMetadata?: { summary: Payload } }, ): EventGroup => { const id = getGroupId(event); const name = getEventGroupName(event); - const { timestamp, category, classification } = event; + const label = getEventGroupLabel(event); + const displayName = getEventGroupDisplayName(event); - const initialEvent = event; + const { timestamp, category, classification } = event; - const events: EventGroup['events'] = new Map(); - const eventIds: EventGroup['eventIds'] = new Set(); + const groupEvents: EventGroup['events'] = new Map(); + const groupEventIds: EventGroup['eventIds'] = new Set(); - events.set(event.id, event); - eventIds.add(event.id); + groupEvents.set(event.id, event); + groupEventIds.add(event.id); return { id, name, - events, - eventIds, - initialEvent, + label, + displayName, + events: groupEvents, + eventIds: groupEventIds, + initialEvent: event, timestamp, - category, + category: isLocalActivityMarkerEvent(event) ? 'local-activity' : category, classification, + level: undefined, + pendingActivity: undefined, + pendingNexusOperation: undefined, + userMetadata: event?.userMetadata, get eventTime() { return this.lastEvent?.eventTime; }, @@ -60,9 +91,25 @@ const createGroupFor = ( get eventList() { return Array.from(this.events, ([_key, value]) => value); }, + get links() { + return Array.from(this.events, ([_key, value]) => value.links).flat(); + }, get lastEvent() { return getLastEvent(this); }, + get finalClassification() { + return getLastEvent(this).classification; + }, + get isPending() { + return ( + !!this.pendingActivity || + !!this.pendingNexusOperation || + (isTimerStartedEvent(this.initialEvent) && + this.eventList.length === 1) || + (isStartChildWorkflowExecutionInitiatedEvent(this.initialEvent) && + this.eventList.length === 2) + ); + }, get isFailureOrTimedOut() { return Boolean(this.eventList.find(eventIsFailureOrTimedOut)); }, @@ -72,6 +119,12 @@ const createGroupFor = ( get isTerminated() { return Boolean(this.eventList.find(eventIsTerminated)); }, + get billableActions() { + return this.eventList.reduce( + (acc, event) => event.billableActions + acc, + 0, + ); + }, }; }; @@ -96,4 +149,17 @@ export const createEventGroup = (event: CommonHistoryEvent): EventGroup => { } return createGroupFor<'Marker'>(event); } + + if (isWorkflowExecutionUpdateAcceptedEvent(event)) + return createGroupFor<'Update'>(event); + + if (isNexusOperationScheduledEvent(event)) + return createGroupFor<'Nexus'>(event); +}; + +export const createWorkflowTaskGroup = ( + event: CommonHistoryEvent, +): EventGroup => { + if (isWorkflowTaskScheduledEvent(event)) + return createGroupFor<'WorkflowTask'>(event); }; diff --git a/src/lib/models/event-groups/event-groups.d.ts b/src/lib/models/event-groups/event-groups.d.ts index a4e12df36..2e3eb402d 100644 --- a/src/lib/models/event-groups/event-groups.d.ts +++ b/src/lib/models/event-groups/event-groups.d.ts @@ -1,3 +1,12 @@ +import type { Payload } from '$lib/types'; +import type { + EventLink, + PendingActivity, + PendingNexusOperation, + WorkflowEvent, +} from '$lib/types/events'; +import type { EventType } from '$lib/utilities/is-event-type'; + type EventId = EventType['id']; interface EventGroup @@ -7,14 +16,24 @@ interface EventGroup > { id: EventId; name: string; + label: string; + displayName: string; events: Map; eventIds: Set; initialEvent: WorkflowEvent; lastEvent: WorkflowEvent; eventList: WorkflowEvent[]; + finalClassification: EventClassification; + isPending: boolean; isFailureOrTimedOut: boolean; isCanceled: boolean; isTerminated: boolean; + level: number | undefined; + pendingActivity: PendingActivity | undefined; + pendingNexusOperation: PendingNexusOperation | undefined; + userMetadata?: { summary?: Payload }; + links: EventLink[]; + billableActions: number; } type EventGroups = EventGroup[]; diff --git a/src/lib/models/event-groups/get-event-in-group.test.ts b/src/lib/models/event-groups/get-event-in-group.test.ts index c998121ef..f59f715fd 100644 --- a/src/lib/models/event-groups/get-event-in-group.test.ts +++ b/src/lib/models/event-groups/get-event-in-group.test.ts @@ -1,4 +1,11 @@ import { describe, expect, it } from 'vitest'; + +import type { + ActivityTaskScheduledEvent, + StartChildWorkflowExecutionInitiatedEvent, + WorkflowEvent, +} from '$lib/types/events'; + import { groupEvents } from '.'; import { eventIsCanceled, @@ -69,6 +76,24 @@ describe('eventIsFailureOrTimedOut', () => { expect(eventIsFailureOrTimedOut(event)).toBe(true); }); + it('should return false if provided an event with workflowTaskFailedEventAttributes and has UnhandledCommand message', () => { + const event = { + workflowTaskFailedEventAttributes: { + failure: { message: 'UnhandledCommand' }, + }, + } as unknown as WorkflowEvent; + expect(eventIsFailureOrTimedOut(event)).toBe(false); + }); + + it('should return false if provided an event with workflowTaskFailedEventAttributes and has resetWorkflowFailureInfo', () => { + const event = { + workflowTaskFailedEventAttributes: { + failure: { resetWorkflowFailureInfo: {} }, + }, + } as unknown as WorkflowEvent; + expect(eventIsFailureOrTimedOut(event)).toBe(false); + }); + it('should return true if provided an event with childWorkflowExecutionFailedEventAttributes', () => { const event = { childWorkflowExecutionFailedEventAttributes: {}, diff --git a/src/lib/models/event-groups/get-event-in-group.ts b/src/lib/models/event-groups/get-event-in-group.ts index 94d4c0041..6fc85482a 100644 --- a/src/lib/models/event-groups/get-event-in-group.ts +++ b/src/lib/models/event-groups/get-event-in-group.ts @@ -1,3 +1,5 @@ +import { isEventGroup } from '$lib/models/event-groups'; +import type { IterableEvent, WorkflowEvent } from '$lib/types/events'; import { isActivityTaskCanceledEvent, isActivityTaskFailedEvent, @@ -6,16 +8,19 @@ import { isChildWorkflowExecutionFailedEvent, isChildWorkflowExecutionTerminatedEvent, isChildWorkflowExecutionTimedOutEvent, + isFailedWorkflowExecutionUpdateCompletedEvent, + isNexusOperationCanceledEvent, + isNexusOperationFailedEvent, + isNexusOperationTimedOutEvent, isSignalExternalWorkflowExecutionFailedEvent, isTimerCanceledEvent, isWorkflowExecutionCanceledEvent, isWorkflowExecutionFailedEvent, isWorkflowExecutionTerminatedEvent, isWorkflowExecutionTimedOutEvent, - isWorkflowTaskTimedOutEvent, isWorkflowTaskFailedEvent, + isWorkflowTaskTimedOutEvent, } from '$lib/utilities/is-event-type'; -import { isEventGroup } from '$lib/models/event-groups'; export const eventIsFailureOrTimedOut = (event: WorkflowEvent): boolean => { return ( @@ -27,7 +32,10 @@ export const eventIsFailureOrTimedOut = (event: WorkflowEvent): boolean => { isWorkflowTaskFailedEvent(event) || isChildWorkflowExecutionFailedEvent(event) || isChildWorkflowExecutionTimedOutEvent(event) || - isSignalExternalWorkflowExecutionFailedEvent(event) + isSignalExternalWorkflowExecutionFailedEvent(event) || + isFailedWorkflowExecutionUpdateCompletedEvent(event) || + isNexusOperationFailedEvent(event) || + isNexusOperationTimedOutEvent(event) ); }; @@ -43,7 +51,8 @@ export const eventIsCanceled = (event: WorkflowEvent): boolean => { isActivityTaskCanceledEvent(event) || isTimerCanceledEvent(event) || isWorkflowExecutionCanceledEvent(event) || - isChildWorkflowExecutionCanceledEvent(event) + isChildWorkflowExecutionCanceledEvent(event) || + isNexusOperationCanceledEvent(event) ); }; diff --git a/src/lib/models/event-groups/get-group-for-event.test.ts b/src/lib/models/event-groups/get-group-for-event.test.ts index 2f26ba35b..01209e68f 100644 --- a/src/lib/models/event-groups/get-group-for-event.test.ts +++ b/src/lib/models/event-groups/get-group-for-event.test.ts @@ -1,5 +1,12 @@ import { describe, expect, it } from 'vitest'; -import { groupEvents, getGroupForEvent } from '.'; + +import type { + ActivityTaskCompletedEvent, + ActivityTaskScheduledEvent, + ActivityTaskStartedEvent, +} from '$lib/types/events'; + +import { getGroupForEvent, groupEvents } from '.'; const eventHistory = [ { diff --git a/src/lib/models/event-groups/get-group-for-event.ts b/src/lib/models/event-groups/get-group-for-event.ts index 691e5a41a..0cb6f6ff3 100644 --- a/src/lib/models/event-groups/get-group-for-event.ts +++ b/src/lib/models/event-groups/get-group-for-event.ts @@ -1,3 +1,7 @@ +import type { WorkflowEvent } from '$lib/types/events'; + +import type { EventGroup, EventGroups } from './event-groups'; + export const getGroupForEvent = ( event: WorkflowEvent, groups: EventGroups, diff --git a/src/lib/models/event-groups/get-group-id.test.ts b/src/lib/models/event-groups/get-group-id.test.ts index 1a5c7e3bf..7efda9845 100644 --- a/src/lib/models/event-groups/get-group-id.test.ts +++ b/src/lib/models/event-groups/get-group-id.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it } from 'vitest'; + +import type { CommonHistoryEvent } from '$lib/types/events'; + import { getGroupId } from './get-group-id'; + import allEventTypesFixture from '$fixtures/all-event-types.json'; const events = allEventTypesFixture as unknown as CommonHistoryEvent[]; @@ -14,11 +18,11 @@ describe('getGroupId', () => { }); it('should get the correct ID for WorkflowTaskCompleted events', () => { - expect(getGroupId(events['WorkflowTaskCompleted'])).toBe('4'); + expect(getGroupId(events['WorkflowTaskCompleted'])).toBe('2'); }); it('should get the correct ID for WorkflowTaskStarted events', () => { - expect(getGroupId(events['WorkflowTaskStarted'])).toBe('3'); + expect(getGroupId(events['WorkflowTaskStarted'])).toBe('2'); }); it('should get the correct ID for WorkflowTaskScheduled events', () => { @@ -74,7 +78,7 @@ describe('getGroupId', () => { }); it('should get the correct ID for ExternalWorkflowExecutionSignaled events', () => { - expect(getGroupId(events['ExternalWorkflowExecutionSignaled'])).toBe('26'); + expect(getGroupId(events['ExternalWorkflowExecutionSignaled'])).toBe('25'); }); it('should get the correct ID for SignalExternalWorkflowExecutionInitiated events', () => { @@ -92,7 +96,7 @@ describe('getGroupId', () => { }); it('should get the correct ID for WorkflowTaskTimedOut events', () => { - expect(getGroupId(events['WorkflowTaskTimedOut'])).toBe('16'); + expect(getGroupId(events['WorkflowTaskTimedOut'])).toBe('15'); }); it('should get the correct ID for ActivityTaskTimedOut events', () => { @@ -152,4 +156,13 @@ describe('getGroupId', () => { } as unknown as CommonHistoryEvent; expect(getGroupId(event)).toBe('42'); }); + + it('should get the correct ID for WorkflowExecutionUpdateCompleted events', () => { + const event = { + workflowExecutionUpdateCompletedEventAttributes: { + acceptedEventId: 59, + }, + } as unknown as CommonHistoryEvent; + expect(getGroupId(event)).toBe('59'); + }); }); diff --git a/src/lib/models/event-groups/get-group-id.ts b/src/lib/models/event-groups/get-group-id.ts index 079ecc639..2727d8a7a 100644 --- a/src/lib/models/event-groups/get-group-id.ts +++ b/src/lib/models/event-groups/get-group-id.ts @@ -1,3 +1,4 @@ +import type { CommonHistoryEvent } from '$lib/types/events'; import { isActivityTaskCanceledEvent, isActivityTaskCancelRequestedEvent, @@ -5,11 +6,24 @@ import { isActivityTaskFailedEvent, isActivityTaskStartedEvent, isActivityTaskTimedOutEvent, + isChildWorkflowExecutionCanceledEvent, isChildWorkflowExecutionCompletedEvent, + isChildWorkflowExecutionFailedEvent, isChildWorkflowExecutionStartedEvent, isChildWorkflowExecutionTerminatedEvent, + isChildWorkflowExecutionTimedOutEvent, + isExternalWorkflowExecutionSignaledEvent, + isNexusOperationCanceledEvent, + isNexusOperationCompletedEvent, + isNexusOperationFailedEvent, + isNexusOperationStartedEvent, + isPureWorkflowTaskFailedEvent, isTimerCanceledEvent, isTimerFiredEvent, + isWorkflowExecutionUpdateCompletedEvent, + isWorkflowTaskCompletedEvent, + isWorkflowTaskStartedEvent, + isWorkflowTaskTimedOutEvent, } from '$lib/utilities/is-event-type'; export const getGroupId = (event: CommonHistoryEvent): string => { @@ -56,6 +70,24 @@ export const getGroupId = (event: CommonHistoryEvent): string => { ); } + if (isChildWorkflowExecutionCanceledEvent(event)) { + return String( + event.childWorkflowExecutionCanceledEventAttributes.initiatedEventId, + ); + } + + if (isChildWorkflowExecutionFailedEvent(event)) { + return String( + event.childWorkflowExecutionFailedEventAttributes.initiatedEventId, + ); + } + + if (isChildWorkflowExecutionTimedOutEvent(event)) { + return String( + event.childWorkflowExecutionTimedOutEventAttributes.initiatedEventId, + ); + } + if (isTimerFiredEvent(event)) { return String(event.timerFiredEventAttributes.startedEventId); } @@ -64,5 +96,51 @@ export const getGroupId = (event: CommonHistoryEvent): string => { return String(event.timerCanceledEventAttributes.startedEventId); } + if (isWorkflowExecutionUpdateCompletedEvent(event)) { + return String( + event.workflowExecutionUpdateCompletedEventAttributes.acceptedEventId, + ); + } + + if (isExternalWorkflowExecutionSignaledEvent(event)) { + return String( + event.externalWorkflowExecutionSignaledEventAttributes.initiatedEventId, + ); + } + + if (isWorkflowTaskStartedEvent(event)) { + return String(event.workflowTaskStartedEventAttributes.scheduledEventId); + } + + if (isWorkflowTaskCompletedEvent(event)) { + return String(event.workflowTaskCompletedEventAttributes.scheduledEventId); + } + + if (isPureWorkflowTaskFailedEvent(event)) { + return String(event.workflowTaskFailedEventAttributes.scheduledEventId); + } + + if (isWorkflowTaskTimedOutEvent(event)) { + return String(event.workflowTaskTimedOutEventAttributes.scheduledEventId); + } + + if (isNexusOperationStartedEvent(event)) { + return String(event.nexusOperationStartedEventAttributes.scheduledEventId); + } + + if (isNexusOperationCompletedEvent(event)) { + return String( + event.nexusOperationCompletedEventAttributes.scheduledEventId, + ); + } + + if (isNexusOperationFailedEvent(event)) { + return String(event.nexusOperationFailedEventAttributes.scheduledEventId); + } + + if (isNexusOperationCanceledEvent(event)) { + return String(event.nexusOperationCanceledEventAttributes.scheduledEventId); + } + return event.id; }; diff --git a/src/lib/models/event-groups/get-group-name.ts b/src/lib/models/event-groups/get-group-name.ts index 9170ed168..02eb4bbf3 100644 --- a/src/lib/models/event-groups/get-group-name.ts +++ b/src/lib/models/event-groups/get-group-name.ts @@ -1,40 +1,114 @@ +import type { CommonHistoryEvent } from '$lib/types/events'; +import { formatDurationAbbreviated } from '$lib/utilities/format-time'; +import { getSummaryAttribute } from '$lib/utilities/get-single-attribute-for-event'; import { isActivityTaskScheduledEvent, + isLocalActivityMarkerEvent, isMarkerRecordedEvent, + isNexusOperationScheduledEvent, + isNexusOperationStartedEvent, isSignalExternalWorkflowExecutionInitiatedEvent, isStartChildWorkflowExecutionInitiatedEvent, - isWorkflowExecutionSignaledEvent, isTimerStartedEvent, - isLocalActivityMarkerEvent, + isWorkflowExecutionSignaledEvent, + isWorkflowExecutionUpdateAcceptedEvent, } from '$lib/utilities/is-event-type'; export const getEventGroupName = (event: CommonHistoryEvent): string => { - if (!event) return; + if (!event) return ''; if (isActivityTaskScheduledEvent(event)) { return event.activityTaskScheduledEventAttributes?.activityType?.name; } if (isTimerStartedEvent(event)) { - return `Timer ${event.timerStartedEventAttributes?.timerId} (${event.timerStartedEventAttributes?.startToFireTimeout})`; + return `${ + event.timerStartedEventAttributes?.timerId + } (${formatDurationAbbreviated( + String(event.timerStartedEventAttributes?.startToFireTimeout), + )})`; } if (isSignalExternalWorkflowExecutionInitiatedEvent(event)) { - return `Signal: ${event.signalExternalWorkflowExecutionInitiatedEventAttributes?.signalName}`; + return event.signalExternalWorkflowExecutionInitiatedEventAttributes + ?.signalName; } if (isWorkflowExecutionSignaledEvent(event)) { - return `Signal received: ${event.workflowExecutionSignaledEventAttributes?.signalName}`; + return event.workflowExecutionSignaledEventAttributes?.signalName; } if (isMarkerRecordedEvent(event)) { if (isLocalActivityMarkerEvent(event)) { return 'Local Activity'; } - return `Marker: ${event.markerRecordedEventAttributes?.markerName}`; + return event.markerRecordedEventAttributes?.markerName; } if (isStartChildWorkflowExecutionInitiatedEvent(event)) { - return `Child Workflow: ${event.startChildWorkflowExecutionInitiatedEventAttributes?.workflowType?.name}`; + return event.startChildWorkflowExecutionInitiatedEventAttributes + ?.workflowType?.name; + } + + if (isWorkflowExecutionUpdateAcceptedEvent(event)) { + return event.workflowExecutionUpdateAcceptedEventAttributes?.acceptedRequest + ?.input?.name; + } + + if (isNexusOperationScheduledEvent(event)) { + return `${event.nexusOperationScheduledEventAttributes.service}.${event.nexusOperationScheduledEventAttributes.operation}`; + } +}; + +export const getEventGroupLabel = (event: CommonHistoryEvent): string => { + if (!event) return ''; + + if (isActivityTaskScheduledEvent(event)) { + return 'Activity'; + } + + if (isTimerStartedEvent(event)) { + return 'Timer'; } + + if (isSignalExternalWorkflowExecutionInitiatedEvent(event)) { + return 'Signal'; + } + + if (isWorkflowExecutionSignaledEvent(event)) { + return 'Signal received'; + } + + if (isLocalActivityMarkerEvent(event)) { + return 'Local Activity'; + } + + if (isMarkerRecordedEvent(event)) { + return 'Marker'; + } + + if (isStartChildWorkflowExecutionInitiatedEvent(event)) { + return 'Child Workflow'; + } + + if (isWorkflowExecutionUpdateAcceptedEvent(event)) { + return 'Workflow Update'; + } + + if ( + isNexusOperationScheduledEvent(event) || + isNexusOperationStartedEvent(event) + ) { + return 'Nexus Operation'; + } +}; + +export const getEventGroupDisplayName = (event: CommonHistoryEvent): string => { + if (!event) return ''; + + if (isLocalActivityMarkerEvent(event)) { + return getSummaryAttribute(event)?.value?.toString() ?? 'Local Activity'; + } + + return getEventGroupName(event); }; diff --git a/src/lib/models/event-groups/get-last-event.test.ts b/src/lib/models/event-groups/get-last-event.test.ts index 1f2226bae..3d2da79a2 100644 --- a/src/lib/models/event-groups/get-last-event.test.ts +++ b/src/lib/models/event-groups/get-last-event.test.ts @@ -1,6 +1,9 @@ -import { getLastEvent } from './get-last-event'; import { describe, expect, it } from 'vitest'; + +import type { WorkflowEvents } from '$lib/types/events'; + import { groupEvents } from '.'; +import { getLastEvent } from './get-last-event'; const scheduledEvent = { id: '5', diff --git a/src/lib/models/event-groups/get-last-event.ts b/src/lib/models/event-groups/get-last-event.ts index 59100ddf1..510cb358f 100644 --- a/src/lib/models/event-groups/get-last-event.ts +++ b/src/lib/models/event-groups/get-last-event.ts @@ -1,3 +1,7 @@ +import type { WorkflowEvent } from '$lib/types/events'; + +import type { EventGroup } from './event-groups'; + export const getLastEvent = ({ events }: EventGroup): WorkflowEvent => { let latestEventKey = 0; let result: WorkflowEvent; diff --git a/src/lib/models/event-groups/group-events.test.ts b/src/lib/models/event-groups/group-events.test.ts index cba71c2ad..f5829ecc9 100644 --- a/src/lib/models/event-groups/group-events.test.ts +++ b/src/lib/models/event-groups/group-events.test.ts @@ -1,6 +1,9 @@ import { describe, expect, it } from 'vitest'; + +import type { CommonHistoryEvent, WorkflowEvents } from '$lib/types/events'; + import { groupEvents } from './'; -import { getEventGroupName } from './get-group-name'; +import { getEventGroupDisplayName } from './get-group-name'; const scheduledEvent = { id: '5', @@ -99,6 +102,24 @@ describe('groupEvents', () => { expect(Object.keys(groups).length).toBe(2); }); + it('should be able to get event groups in ascending order by default', () => { + const groups = groupEvents([ + scheduledEvent, + anotherScheduledEvent, + ] as unknown as WorkflowEvents); + + expect(groups[0].id).toBe(scheduledEvent.id); + }); + + it('should be able to get event groups in descending order', () => { + const groups = groupEvents( + [scheduledEvent, anotherScheduledEvent] as unknown as WorkflowEvents, + 'descending', + ); + + expect(groups[0].id).toBe(anotherScheduledEvent.id); + }); + it('should add a completed event to the correct group', () => { const groups = groupEvents([ scheduledEvent, @@ -111,6 +132,18 @@ describe('groupEvents', () => { expect(group.events.get(completedEvent.id)).toBe(completedEvent); }); + it('should add a completed event to the correct group in descending order', () => { + const groups = groupEvents( + [completedEvent, scheduledEvent] as unknown as WorkflowEvents, + 'descending', + ); + + const group = groups.find(({ id }) => id === scheduledEvent.id); + + expect(group.events.size).toBe(2); + expect(group.events.get(completedEvent.id)).toBe(completedEvent); + }); + it('should be able to add multiple event groups and their associated events', () => { const groups = groupEvents(eventHistory); @@ -122,14 +155,14 @@ describe('groupEvents', () => { }); }); -describe('getEventGroupName', () => { +describe('getEventGroupDisplayName', () => { it('should get the name of the eventGroup', () => { const [group] = groupEvents(eventHistory); expect(group.name).toBe('CompletedActivity'); }); it('should guard against empty arguments', () => { - expect(getEventGroupName(undefined as CommonHistoryEvent)).toBeUndefined(); + expect(getEventGroupDisplayName(undefined as CommonHistoryEvent)).toBe(''); }); it('should get the name of a TimerStartedEvent', () => { @@ -144,7 +177,7 @@ describe('getEventGroupName', () => { name: 'TimerStarted', } as unknown as CommonHistoryEvent; - expect(getEventGroupName(timerStartedEvent)).toBe('Timer 8 (4s)'); + expect(getEventGroupDisplayName(timerStartedEvent)).toBe('8 (4s)'); }); it('should get the name of a SignalExternalWorkflowExecutionInitiatedEvent', () => { @@ -155,7 +188,7 @@ describe('getEventGroupName', () => { }, name: 'WorkflowExecutionSignaled', } as unknown as CommonHistoryEvent; - expect(getEventGroupName(signalEvent)).toBe('Signal: WorkflowSignal'); + expect(getEventGroupDisplayName(signalEvent)).toBe('WorkflowSignal'); }); it('should get the name of a WorkflowExecutionSignaledEvent', () => { @@ -167,8 +200,8 @@ describe('getEventGroupName', () => { name: 'WorkflowExecutionSignaled', } as unknown as CommonHistoryEvent; - expect(getEventGroupName(workflowExectutionSignaledEvent)).toBe( - 'Signal received: signalBeforeReset', + expect(getEventGroupDisplayName(workflowExectutionSignaledEvent)).toBe( + 'signalBeforeReset', ); }); @@ -185,7 +218,7 @@ describe('getEventGroupName', () => { timestamp: '2022-07-01 UTC 20:23:49.13', } as unknown as CommonHistoryEvent; - expect(getEventGroupName(markerRecordedEvent)).toBe('Marker: Version'); + expect(getEventGroupDisplayName(markerRecordedEvent)).toBe('Version'); }); it('should get the name of a StartChildWorkflowExecutionInitiatedEvent', () => { @@ -197,8 +230,56 @@ describe('getEventGroupName', () => { }, } as unknown as CommonHistoryEvent; - expect(getEventGroupName(startChildWorkflowEvent)).toBe( - 'Child Workflow: Workflow Name', + expect(getEventGroupDisplayName(startChildWorkflowEvent)).toBe( + 'Workflow Name', + ); + }); + + it('should get the name of a WorkflowExecutionUpdateAcceptedEvent', () => { + const workflowUpdateAcceptedEvent = { + workflowExecutionUpdateAcceptedEventAttributes: { + acceptedRequest: { + input: { + name: 'Start the update', + }, + }, + }, + } as unknown as CommonHistoryEvent; + + expect(getEventGroupDisplayName(workflowUpdateAcceptedEvent)).toBe( + 'Start the update', ); }); + + it('should get the name of a Local Activity from payload', () => { + const localActivityPayload = { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZA==', + }, + data: 'eyAiYWN0aXZpdHlfdHlwZSI6ICJjYXRzIiB9', + }; + + const localActivityEvent = { + eventType: 'MarkerRecorded', + markerRecordedEventAttributes: { + markerName: 'core_local_activity', + details: { + data: { + payloads: [localActivityPayload], + }, + }, + }, + attributes: { + markerName: 'core_local_activity', + details: { + data: { + payloads: [localActivityPayload], + }, + }, + }, + } as unknown as CommonHistoryEvent; + + expect(getEventGroupDisplayName(localActivityEvent)).toBe('cats'); + }); }); diff --git a/src/lib/models/event-groups/index.ts b/src/lib/models/event-groups/index.ts index 2259a4e78..4daa701dd 100644 --- a/src/lib/models/event-groups/index.ts +++ b/src/lib/models/event-groups/index.ts @@ -1,33 +1,112 @@ +import type { EventSortOrder } from '$lib/stores/event-view'; +import type { + CommonHistoryEvent, + PendingActivity, + PendingNexusOperation, + WorkflowEvent, +} from '$lib/types/events'; import { has } from '$lib/utilities/has'; -import { createEventGroup } from './create-event-group'; +import { + isNexusOperationCanceledEvent, + isNexusOperationCompletedEvent, + isNexusOperationFailedEvent, +} from '$lib/utilities/is-event-type'; +import { + getPendingActivity, + getPendingNexusOperation, +} from '$lib/utilities/pending-activities'; + +import { + createEventGroup, + createWorkflowTaskGroup, +} from './create-event-group'; +import type { EventGroup, EventGroups } from './event-groups'; import { getGroupId } from './get-group-id'; export { getGroupForEvent } from './get-group-for-event'; -const addToExistingGroup = (group: EventGroup, event: WorkflowEvent): void => { +const addToExistingGroup = ( + group: EventGroup, + event: WorkflowEvent, + pendingActivity: PendingActivity | undefined = undefined, + pendingNexusOperation: PendingNexusOperation | undefined = undefined, +): void => { if (!group) return; group.events.set(event.id, event); group.eventIds.add(event.id); group.timestamp = event.timestamp; + + if (pendingActivity) { + group.pendingActivity = pendingActivity; + } + + if (group.pendingActivity && group.eventList.length === 3) { + delete group.pendingActivity; + } + + if (pendingNexusOperation) { + group.pendingNexusOperation = pendingNexusOperation; + } + + const completedNexusEvent = + isNexusOperationCompletedEvent(event) || + isNexusOperationFailedEvent(event) || + isNexusOperationCanceledEvent(event); + if (group.pendingNexusOperation && completedNexusEvent) { + delete group.pendingNexusOperation; + } }; -export const groupEvents = (events: CommonHistoryEvent[]): EventGroups => { +export const groupEvents = ( + events: CommonHistoryEvent[], + sort: EventSortOrder = 'ascending', + pendingActivities: PendingActivity[] = [], + pendingNexusOperations: PendingNexusOperation[] = [], +): EventGroups => { const groups: Record = {}; - for (const event of events) { + const createGroups = (event: CommonHistoryEvent) => { const id = getGroupId(event); const group = createEventGroup(event); + const pendingActivity = getPendingActivity(event, pendingActivities); + const pendingNexusOperation = getPendingNexusOperation( + event, + pendingNexusOperations, + ); if (group) { groups[group.id] = group; + if (pendingActivity) { + group.pendingActivity = pendingActivity; + } + if (pendingNexusOperation) { + group.pendingNexusOperation = pendingNexusOperation; + } } else { - addToExistingGroup(groups[id], event); + addToExistingGroup( + groups[id], + event, + pendingActivity, + pendingNexusOperation, + ); + } + }; + + if (sort === 'ascending') { + for (const event of events) { + createGroups(event); + } + } else { + for (let i = events.length - 1; i >= 0; i--) { + createGroups(events[i]); } } - return Object.values(groups); + return sort === 'descending' + ? Object.values(groups).reverse() + : Object.values(groups); }; export const isEventGroup = ( @@ -43,3 +122,35 @@ export const isEventGroups = ( if (eventsOrGroups === undefined || eventsOrGroups === null) return false; return eventsOrGroups.every(isEventGroup); }; + +export const groupWorkflowTaskEvents = ( + events: CommonHistoryEvent[], + sort: EventSortOrder = 'ascending', +): EventGroups => { + const groups: Record = {}; + + const createGroups = (event: CommonHistoryEvent) => { + const id = getGroupId(event); + const group = createWorkflowTaskGroup(event); + + if (group) { + groups[group.id] = group; + } else { + addToExistingGroup(groups[id], event); + } + }; + + if (sort === 'ascending') { + for (const event of events) { + createGroups(event); + } + } else { + for (let i = events.length - 1; i >= 0; i--) { + createGroups(events[i]); + } + } + + return sort === 'descending' + ? Object.values(groups).reverse() + : Object.values(groups); +}; diff --git a/src/lib/models/event-groups/is-event-groups.test.ts b/src/lib/models/event-groups/is-event-groups.test.ts index 467b961b9..62fcd68f0 100644 --- a/src/lib/models/event-groups/is-event-groups.test.ts +++ b/src/lib/models/event-groups/is-event-groups.test.ts @@ -1,5 +1,7 @@ import { describe, expect, it } from 'vitest'; -import { isEventGroups, isEventGroup } from '.'; + +import { isEventGroup, isEventGroups } from '.'; +import type { EventGroup, EventGroups } from './event-groups'; import eventGroupsFixture from '$fixtures/event-groups.completed.json'; import eventsFixture from '$fixtures/events.completed.json'; diff --git a/src/lib/models/event-history/__snapshots__/get-event-categorization.test.ts.snap b/src/lib/models/event-history/__snapshots__/get-event-categorization.test.ts.snap index 842310c08..8448bede1 100644 --- a/src/lib/models/event-history/__snapshots__/get-event-categorization.test.ts.snap +++ b/src/lib/models/event-history/__snapshots__/get-event-categorization.test.ts.snap @@ -1,4 +1,4 @@ -// Vitest Snapshot v1 +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`getEventsInCategory > should return the correct events for the activity" category 1`] = ` [ @@ -138,115 +138,7 @@ exports[`getEventsInCategory > should return the correct events for the activity exports[`getEventsInCategory > should return the correct events for the child-workflow" category 1`] = `[]`; -exports[`getEventsInCategory > should return the correct events for the command" category 1`] = ` -[ - { - "attributes": { - "searchAttributes": { - "indexedFields": { - "TemporalChangeVersion": [ - "initial version-3", - ], - }, - }, - "type": "upsertWorkflowSearchAttributesEventAttributes", - "workflowTaskCompletedEventId": "4", - }, - "category": "command", - "eventId": "6", - "eventTime": "2022-07-01T20:28:48.821619794Z", - "eventType": "UpsertWorkflowSearchAttributes", - "id": "6", - "name": "UpsertWorkflowSearchAttributes", - "taskId": "29887308", - "timestamp": "2022-07-01 UTC 20:28:48.82", - "upsertWorkflowSearchAttributesEventAttributes": { - "searchAttributes": { - "indexedFields": { - "TemporalChangeVersion": [ - "initial version-3", - ], - }, - }, - "workflowTaskCompletedEventId": "4", - }, - "version": "0", - }, -] -`; - -exports[`getEventsInCategory > should return the correct events for the marker" category 1`] = ` -[ - { - "attributes": { - "details": { - "change-id": { - "payloads": [ - { - "data": "ImluaXRpYWwgdmVyc2lvbiI=", - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - }, - }, - ], - }, - "version": { - "payloads": [ - { - "data": "Mw==", - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - }, - }, - ], - }, - }, - "failure": null, - "header": null, - "markerName": "Version", - "type": "markerRecordedEventAttributes", - "workflowTaskCompletedEventId": "4", - }, - "category": "marker", - "eventId": "5", - "eventTime": "2022-07-01T20:28:48.820951669Z", - "eventType": "MarkerRecorded", - "id": "5", - "markerRecordedEventAttributes": { - "details": { - "change-id": { - "payloads": [ - { - "data": "ImluaXRpYWwgdmVyc2lvbiI=", - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - }, - }, - ], - }, - "version": { - "payloads": [ - { - "data": "Mw==", - "metadata": { - "encoding": "anNvbi9wbGFpbg==", - }, - }, - ], - }, - }, - "failure": null, - "header": null, - "markerName": "Version", - "workflowTaskCompletedEventId": "4", - }, - "name": "MarkerRecorded", - "taskId": "29887307", - "timestamp": "2022-07-01 UTC 20:28:48.82", - "version": "0", - }, -] -`; +exports[`getEventsInCategory > should return the correct events for the other" category 1`] = `[]`; exports[`getEventsInCategory > should return the correct events for the signal" category 1`] = `[]`; diff --git a/src/lib/models/event-history/get-event-attributes.test.ts b/src/lib/models/event-history/get-event-attributes.test.ts index 4011241f1..e22a89cd6 100644 --- a/src/lib/models/event-history/get-event-attributes.test.ts +++ b/src/lib/models/event-history/get-event-attributes.test.ts @@ -1,9 +1,14 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { writable } from 'svelte/store'; + +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import type { HistoryEvent } from '$lib/types/events'; +import type { Settings } from '$lib/types/global'; + import { getEventAttributes, toEventHistory } from '.'; -import settingsFixture from '$fixtures/settings.json'; import eventsFixture from '$fixtures/raw-events.descending.completed.json'; +import settingsFixture from '$fixtures/settings.json'; const historyEvent = { eventId: '1', @@ -86,12 +91,10 @@ describe('getEventAttributes', () => { const fn = async (x: T): Promise => x; const convertPayloadToJsonWithCodec = vi.fn(fn); - const convertPayloadToJsonWithWebsocket = vi.fn(fn); const decodePayloadAttributes = vi.fn(fn); return { convertPayloadToJsonWithCodec, - convertPayloadToJsonWithWebsocket, decodePayloadAttributes, }; }); @@ -112,9 +115,8 @@ describe('getEventAttributes', () => { expect(event.type).toBe(eventType); }); - it('should call convertWithWebSocket if not provided an endpoint', async () => { + it('should call convertWithCodec if not provided an endpoint', async () => { const convertWithCodec = vi.fn(async (x: T): Promise => x); - const convertWithWebsocket = vi.fn(async (x: T): Promise => x); await getEventAttributes( { @@ -124,18 +126,15 @@ describe('getEventAttributes', () => { accessToken, }, { - convertWithWebsocket, convertWithCodec, }, ); - expect(convertWithWebsocket).toBeCalled(); - expect(convertWithCodec).not.toBeCalled(); + expect(convertWithCodec).toBeCalled(); }); it('should call convertWithCodec if provided an endpoint in settings', async () => { const convertWithCodec = vi.fn(async (x: T): Promise => x); - const convertWithWebsocket = vi.fn(async (x: T): Promise => x); await getEventAttributes( { @@ -145,18 +144,15 @@ describe('getEventAttributes', () => { accessToken, }, { - convertWithWebsocket, convertWithCodec, }, ); expect(convertWithCodec).toBeCalled(); - expect(convertWithWebsocket).not.toBeCalled(); }); it('should call convertWithCodec if provided an endpoint in settings', async () => { const convertWithCodec = vi.fn(async (x: T): Promise => x); - const convertWithWebsocket = vi.fn(async (x: T): Promise => x); await getEventAttributes( { @@ -166,14 +162,12 @@ describe('getEventAttributes', () => { accessToken, }, { - convertWithWebsocket, convertWithCodec, encoderEndpoint: writable('https://localhost'), }, ); expect(convertWithCodec).toBeCalled(); - expect(convertWithWebsocket).not.toBeCalled(); }); }); @@ -190,12 +184,9 @@ describe('toEventHistory', () => { for (const property of additionalProperties) { it(`should add a[n] ${property} property`, async () => { - const { events } = await toEventHistory({ - response: eventsFixture.history.events as unknown as HistoryEvent[], - namespace, - settings, - accessToken, - }); + const events = await toEventHistory( + eventsFixture.history.events as unknown as HistoryEvent[], + ); const [event] = events; diff --git a/src/lib/models/event-history/get-event-billable-actions.ts b/src/lib/models/event-history/get-event-billable-actions.ts new file mode 100644 index 000000000..2d8242584 --- /dev/null +++ b/src/lib/models/event-history/get-event-billable-actions.ts @@ -0,0 +1,99 @@ +import type { WorkflowEvent } from '$lib/types/events'; +import { isWorkflowTaskFailedEventDueToReset } from '$lib/utilities/get-workflow-task-failed-event'; +import { + isActivityTaskScheduledEvent, + isActivityTaskStartedEvent, + isMarkerRecordedEvent, + isNexusOperationCancelRequestedEvent, + isNexusOperationScheduledEvent, + isStartChildWorkflowExecutionInitiatedEvent, + isTimerStartedEvent, + isUpsertWorkflowSearchAttributesEvent, + isWorkflowExecutionOptionsUpdatedEvent, + isWorkflowExecutionSignaledEvent, + isWorkflowExecutionStartedEvent, + isWorkflowExecutionUpdateAcceptedEvent, + isWorkflowExecutionUpdateRejectedEvent, + isWorkflowTaskCompletedEvent, +} from '$lib/utilities/is-event-type'; + +export const getEventBillableActions = ( + event: WorkflowEvent, + processedWorkflowTaskIds?: Set, +): number => { + try { + if (isWorkflowExecutionStartedEvent(event)) { + // Charge 2 additional for scheduled workflows if first run + const firstRunId = event.attributes?.firstExecutionRunId; + const currentRunId = event.attributes?.originalExecutionRunId; + const isFirstRun = firstRunId === currentRunId; + const isScheduledFirstRun = + isFirstRun && + event.attributes?.searchAttributes?.indexedFields + ?.TemporalScheduledById; + if (isScheduledFirstRun) return 3; + return 1; + } + if (isActivityTaskScheduledEvent(event)) return 1; + if (isTimerStartedEvent(event)) return 1; + // Don't charge for signaled with start workflows + if (isWorkflowExecutionSignaledEvent(event) && event.id !== '2') return 1; + if (isNexusOperationScheduledEvent(event)) return 1; + if (isNexusOperationCancelRequestedEvent(event)) return 1; + if (isWorkflowExecutionUpdateAcceptedEvent(event)) return 1; + if (isWorkflowExecutionUpdateRejectedEvent(event)) return 1; + if (isWorkflowExecutionOptionsUpdatedEvent(event)) return 1; + + if (isUpsertWorkflowSearchAttributesEvent(event)) { + const searchAttributeFields = Object.keys( + event.attributes.searchAttributes.indexedFields, + ); + if ( + searchAttributeFields?.length === 1 && + event.attributes?.searchAttributes?.indexedFields?.TemporalChangeVersion + ) { + // Non-billable search attribute update + return 0; + } + return 1; + } + + if (isMarkerRecordedEvent(event)) { + const nonBillable = ['core_patch', 'Version']; + if (nonBillable.includes(event?.attributes?.markerName)) return 0; + + // Check if any other markers are associated with same workflow task. If so, only charge for one marker, not all of them for the workflow task + const workflowTaskId = event.attributes?.workflowTaskCompletedEventId; + if (workflowTaskId && processedWorkflowTaskIds) { + if (processedWorkflowTaskIds.has(String(workflowTaskId))) { + return 0; + } + processedWorkflowTaskIds.add(String(workflowTaskId)); + return 1; + } + + if (workflowTaskId) return 1; + } + + if (isWorkflowTaskFailedEventDueToReset(event)) return 1; + + if (isStartChildWorkflowExecutionInitiatedEvent(event)) return 2; + + if (isActivityTaskStartedEvent(event)) { + const attempts = event.attributes?.attempt || 1; + return attempts - 1; + } + + if (isWorkflowTaskCompletedEvent(event)) { + return Math.min( + event.attributes?.meteringMetadata + ?.nonfirstLocalActivityExecutionAttempts || 0, + 100, + ); + } + + return 0; + } catch { + return 0; + } +}; diff --git a/src/lib/models/event-history/get-event-categorization.test.ts b/src/lib/models/event-history/get-event-categorization.test.ts index 5511c5a63..079804c11 100644 --- a/src/lib/models/event-history/get-event-categorization.test.ts +++ b/src/lib/models/event-history/get-event-categorization.test.ts @@ -1,5 +1,11 @@ import { describe, expect, it } from 'vitest'; -import eventsCompleted from '$fixtures/events.completed.json'; + +import type { + EventType, + EventTypeCategory, + WorkflowEvents, +} from '$lib/types/events'; + import { allEventTypeOptions, compactEventTypeOptions, @@ -7,9 +13,10 @@ import { getEventCategory, getEventsInCategory, isCategoryType, - timelineEventTypeOptions, } from './get-event-categorization'; +import eventsCompleted from '$fixtures/events.completed.json'; + describe('Event Category Data Structures', () => { it('should match the eventTypeCategorizations to the last snapshot', () => { expect(eventTypeCategorizations).toMatchInlineSnapshot(` @@ -28,8 +35,17 @@ describe('Event Category Data Structures', () => { "ChildWorkflowExecutionTerminated": "child-workflow", "ChildWorkflowExecutionTimedOut": "child-workflow", "ExternalWorkflowExecutionCancelRequested": "workflow", - "ExternalWorkflowExecutionSignaled": "workflow", - "MarkerRecorded": "marker", + "ExternalWorkflowExecutionSignaled": "signal", + "MarkerRecorded": "other", + "NexusOperationCancelRequestCompleted": "nexus", + "NexusOperationCancelRequestFailed": "nexus", + "NexusOperationCancelRequested": "nexus", + "NexusOperationCanceled": "nexus", + "NexusOperationCompleted": "nexus", + "NexusOperationFailed": "nexus", + "NexusOperationScheduled": "nexus", + "NexusOperationStarted": "nexus", + "NexusOperationTimedOut": "nexus", "RequestCancelExternalWorkflowExecutionFailed": "workflow", "RequestCancelExternalWorkflowExecutionInitiated": "workflow", "SignalExternalWorkflowExecutionFailed": "signal", @@ -39,16 +55,23 @@ describe('Event Category Data Structures', () => { "TimerCanceled": "timer", "TimerFired": "timer", "TimerStarted": "timer", - "UpsertWorkflowSearchAttributes": "command", + "UpsertWorkflowSearchAttributes": "other", "WorkflowExecutionCancelRequested": "workflow", "WorkflowExecutionCanceled": "workflow", "WorkflowExecutionCompleted": "workflow", "WorkflowExecutionContinuedAsNew": "workflow", "WorkflowExecutionFailed": "workflow", + "WorkflowExecutionOptionsUpdated": "workflow", "WorkflowExecutionSignaled": "signal", "WorkflowExecutionStarted": "workflow", "WorkflowExecutionTerminated": "workflow", "WorkflowExecutionTimedOut": "workflow", + "WorkflowExecutionUpdateAccepted": "update", + "WorkflowExecutionUpdateAdmitted": "update", + "WorkflowExecutionUpdateCompleted": "update", + "WorkflowExecutionUpdateRejected": "update", + "WorkflowExecutionUpdateRequested": "update", + "WorkflowPropertiesModified": "other", "WorkflowTaskCompleted": "workflow", "WorkflowTaskFailed": "workflow", "WorkflowTaskScheduled": "workflow", @@ -62,43 +85,49 @@ describe('Event Category Data Structures', () => { expect(allEventTypeOptions).toMatchInlineSnapshot(` [ { - "label": "All", - "option": undefined, + "description": "events.category.activity-tooltip", + "label": "events.category.activity", + "value": "activity", }, { - "color": "#8B5CF6", - "label": "Activity", - "option": "activity", + "description": "events.category.child-workflow-tooltip", + "label": "events.category.child-workflow", + "value": "child-workflow", }, { - "color": "#F59E0B", - "label": "Child Workflow", - "option": "child-workflow", + "description": "events.category.local-activity-tooltip", + "label": "events.category.local-activity", + "value": "local-activity", }, { - "color": "#10B981", - "label": "Command", - "option": "command", + "description": "events.category.signal-tooltip", + "label": "events.category.signal", + "value": "signal", }, { - "color": "#EC4899", - "label": "Marker", - "option": "marker", + "description": "events.category.timer-tooltip", + "label": "events.category.timer", + "value": "timer", }, { - "color": "#DD6B20", - "label": "Signal", - "option": "signal", + "description": "events.category.update-tooltip", + "label": "events.category.update", + "value": "update", }, { - "color": "#1D4ED8", - "label": "Timer", - "option": "timer", + "description": "events.category.nexus-tooltip", + "label": "events.category.nexus", + "value": "nexus", }, { - "color": "#10B981", - "label": "Workflow", - "option": "workflow", + "description": "events.category.workflow-tooltip", + "label": "events.category.workflow", + "value": "workflow", + }, + { + "description": "events.category.other-tooltip", + "label": "events.category.other", + "value": "other", }, ] `); @@ -108,30 +137,54 @@ describe('Event Category Data Structures', () => { expect(compactEventTypeOptions).toMatchInlineSnapshot(` [ { - "label": "All", - "option": undefined, + "description": "events.category.activity-tooltip", + "label": "events.category.activity", + "value": "activity", + }, + { + "description": "events.category.child-workflow-tooltip", + "label": "events.category.child-workflow", + "value": "child-workflow", + }, + { + "description": "events.category.local-activity-tooltip", + "label": "events.category.local-activity", + "value": "local-activity", }, { - "color": "#8B5CF6", - "label": "Activity", - "option": "activity", + "description": "events.category.signal-tooltip", + "label": "events.category.signal", + "value": "signal", }, { - "color": "#DD6B20", - "label": "Signal", - "option": "signal", + "description": "events.category.timer-tooltip", + "label": "events.category.timer", + "value": "timer", }, { - "color": "#1D4ED8", - "label": "Timer", - "option": "timer", + "description": "events.category.update-tooltip", + "label": "events.category.update", + "value": "update", + }, + { + "description": "events.category.nexus-tooltip", + "label": "events.category.nexus", + "value": "nexus", + }, + { + "description": "events.category.other-tooltip", + "label": "events.category.other", + "value": "other", }, ] `); }); }); -const categories: Record = { +const categories: Record< + Exclude, + EventType[] +> = { activity: [ 'ActivityTaskCanceled', 'ActivityTaskCancelRequested', @@ -153,12 +206,11 @@ const categories: Record = { 'StartChildWorkflowExecutionInitiated', ], - marker: ['MarkerRecorded'], - signal: [ 'SignalExternalWorkflowExecutionFailed', 'SignalExternalWorkflowExecutionInitiated', 'WorkflowExecutionSignaled', + 'ExternalWorkflowExecutionSignaled', ], timer: ['TimerCanceled', 'TimerFired', 'TimerStarted'], @@ -178,12 +230,31 @@ const categories: Record = { 'WorkflowTaskStarted', 'WorkflowTaskTimedOut', 'ExternalWorkflowExecutionCancelRequested', - 'ExternalWorkflowExecutionSignaled', 'RequestCancelExternalWorkflowExecutionFailed', 'RequestCancelExternalWorkflowExecutionInitiated', ], - command: ['UpsertWorkflowSearchAttributes'], + update: [ + 'WorkflowExecutionUpdateAccepted', + 'WorkflowExecutionUpdateAdmitted', + 'WorkflowExecutionUpdateCompleted', + ], + + nexus: [ + 'NexusOperationScheduled', + 'NexusOperationStarted', + 'NexusOperationCompleted', + 'NexusOperationFailed', + 'NexusOperationCanceled', + 'NexusOperationTimedOut', + 'NexusOperationCancelRequested', + ], + + other: [ + 'MarkerRecorded', + 'UpsertWorkflowSearchAttributes', + 'WorkflowPropertiesModified', + ], }; describe('getEventCategory', () => { @@ -196,6 +267,10 @@ describe('getEventCategory', () => { } }); +it('should return other for unknown eventType', () => { + expect(getEventCategory('crazyUnknownNewEvent' as EventType)).toBe('other'); +}); + describe('getEventsInCategory', () => { const events = eventsCompleted as unknown as WorkflowEvents; @@ -209,14 +284,6 @@ describe('getEventsInCategory', () => { expect(getEventsInCategory(events, 'child-workflow')).toMatchSnapshot(); }); - it('should return the correct events for the command" category', () => { - expect(getEventsInCategory(events, 'command')).toMatchSnapshot(); - }); - - it('should return the correct events for the marker" category', () => { - expect(getEventsInCategory(events, 'marker')).toMatchSnapshot(); - }); - it('should return the correct events for the signal" category', () => { expect(getEventsInCategory(events, 'signal')).toMatchSnapshot(); }); @@ -229,6 +296,10 @@ describe('getEventsInCategory', () => { expect(getEventsInCategory(events, 'workflow')).toMatchSnapshot(); }); + it('should return the correct events for the other" category', () => { + expect(getEventsInCategory(events, 'other')).toMatchSnapshot(); + }); + it('should return the original set of events if given an invalid category', () => { expect(getEventsInCategory(events, 'bogus')).toEqual(events); }); @@ -255,48 +326,19 @@ describe('isCategoryType', () => { expect(isCategoryType('workflow')).toBeTruthy(); }); - it('should return true for "command"', () => { - expect(isCategoryType('command')).toBeTruthy(); + it('should return true for "nexus"', () => { + expect(isCategoryType('nexus')).toBeTruthy(); }); - it('should return false for "bogus"', () => { - expect(isCategoryType('bogus')).toBe(false); + it('should return true for "local-activity"', () => { + expect(isCategoryType('local-activity')).toBeTruthy(); }); -}); -describe('timelineEventTypeOptions', () => { - it('should match return valid timeline event types', () => { - expect(timelineEventTypeOptions).toEqual([ - { - color: '#8B5CF6', - label: 'Activity', - option: 'activity', - }, - { - color: '#F59E0B', - label: 'Child Workflow', - option: 'child-workflow', - }, - { - color: '#10B981', - label: 'Command', - option: 'command', - }, - { - color: '#EC4899', - label: 'Marker', - option: 'marker', - }, - { - color: '#DD6B20', - label: 'Signal', - option: 'signal', - }, - { - color: '#1D4ED8', - label: 'Timer', - option: 'timer', - }, - ]); + it('should return true for "other"', () => { + expect(isCategoryType('other')).toBeTruthy(); + }); + + it('should return false for "bogus"', () => { + expect(isCategoryType('bogus')).toBe(false); }); }); diff --git a/src/lib/models/event-history/get-event-categorization.ts b/src/lib/models/event-history/get-event-categorization.ts index 5ed7a5bda..41a579720 100644 --- a/src/lib/models/event-history/get-event-categorization.ts +++ b/src/lib/models/event-history/get-event-categorization.ts @@ -1,113 +1,168 @@ +import type { I18nKey } from '$lib/i18n'; +import type { + EventType, + IterableEvent, + WorkflowEvents, +} from '$lib/types/events'; + +type Categories = typeof CATEGORIES; +export type EventTypeCategory = Categories[keyof Categories]; + +export const CATEGORIES = { + ACTIVITY: 'activity', + CHILD_WORKFLOW: 'child-workflow', + LOCAL_ACTIVITY: 'local-activity', + NEXUS: 'nexus', + SIGNAL: 'signal', + TIMER: 'timer', + UPDATE: 'update', + WORKFLOW: 'workflow', + OTHER: 'other', +} as const; + export const eventTypeCategorizations: Readonly< Record > = { - ActivityTaskCanceled: 'activity', - ActivityTaskCancelRequested: 'activity', - ActivityTaskCompleted: 'activity', - ActivityTaskFailed: 'activity', - ActivityTaskScheduled: 'activity', - ActivityTaskStarted: 'activity', - ActivityTaskTimedOut: 'activity', - - ChildWorkflowExecutionCanceled: 'child-workflow', - ChildWorkflowExecutionCompleted: 'child-workflow', - ChildWorkflowExecutionFailed: 'child-workflow', - ChildWorkflowExecutionStarted: 'child-workflow', - ChildWorkflowExecutionTerminated: 'child-workflow', - ChildWorkflowExecutionTimedOut: 'child-workflow', - StartChildWorkflowExecutionFailed: 'child-workflow', - StartChildWorkflowExecutionInitiated: 'child-workflow', - - MarkerRecorded: 'marker', - - SignalExternalWorkflowExecutionFailed: 'signal', - SignalExternalWorkflowExecutionInitiated: 'signal', - WorkflowExecutionSignaled: 'signal', - - TimerCanceled: 'timer', - TimerFired: 'timer', - TimerStarted: 'timer', - - WorkflowExecutionCanceled: 'workflow', - WorkflowExecutionCancelRequested: 'workflow', - WorkflowExecutionCompleted: 'workflow', - WorkflowExecutionContinuedAsNew: 'workflow', - WorkflowExecutionFailed: 'workflow', - WorkflowExecutionStarted: 'workflow', - WorkflowExecutionTerminated: 'workflow', - WorkflowExecutionTimedOut: 'workflow', - WorkflowTaskCompleted: 'workflow', - WorkflowTaskFailed: 'workflow', - WorkflowTaskScheduled: 'workflow', - WorkflowTaskStarted: 'workflow', - WorkflowTaskTimedOut: 'workflow', - ExternalWorkflowExecutionCancelRequested: 'workflow', - ExternalWorkflowExecutionSignaled: 'workflow', - RequestCancelExternalWorkflowExecutionFailed: 'workflow', - RequestCancelExternalWorkflowExecutionInitiated: 'workflow', - - UpsertWorkflowSearchAttributes: 'command', -}; + ActivityTaskCanceled: CATEGORIES.ACTIVITY, + ActivityTaskCancelRequested: CATEGORIES.ACTIVITY, + ActivityTaskCompleted: CATEGORIES.ACTIVITY, + ActivityTaskFailed: CATEGORIES.ACTIVITY, + ActivityTaskScheduled: CATEGORIES.ACTIVITY, + ActivityTaskStarted: CATEGORIES.ACTIVITY, + ActivityTaskTimedOut: CATEGORIES.ACTIVITY, + + ChildWorkflowExecutionCanceled: CATEGORIES.CHILD_WORKFLOW, + ChildWorkflowExecutionCompleted: CATEGORIES.CHILD_WORKFLOW, + ChildWorkflowExecutionFailed: CATEGORIES.CHILD_WORKFLOW, + ChildWorkflowExecutionStarted: CATEGORIES.CHILD_WORKFLOW, + ChildWorkflowExecutionTerminated: CATEGORIES.CHILD_WORKFLOW, + ChildWorkflowExecutionTimedOut: CATEGORIES.CHILD_WORKFLOW, + StartChildWorkflowExecutionFailed: CATEGORIES.CHILD_WORKFLOW, + StartChildWorkflowExecutionInitiated: CATEGORIES.CHILD_WORKFLOW, + + SignalExternalWorkflowExecutionFailed: CATEGORIES.SIGNAL, + SignalExternalWorkflowExecutionInitiated: CATEGORIES.SIGNAL, + WorkflowExecutionSignaled: CATEGORIES.SIGNAL, + ExternalWorkflowExecutionSignaled: CATEGORIES.SIGNAL, + + TimerCanceled: CATEGORIES.TIMER, + TimerFired: CATEGORIES.TIMER, + TimerStarted: CATEGORIES.TIMER, -export type EventTypeCategory = typeof categories[number]; -const categories = [ - 'activity', - 'child-workflow', - 'command', - 'marker', - 'signal', - 'timer', - 'workflow', -] as const; + WorkflowExecutionCanceled: CATEGORIES.WORKFLOW, + WorkflowExecutionCancelRequested: CATEGORIES.WORKFLOW, + WorkflowExecutionCompleted: CATEGORIES.WORKFLOW, + WorkflowExecutionContinuedAsNew: CATEGORIES.WORKFLOW, + WorkflowExecutionFailed: CATEGORIES.WORKFLOW, + WorkflowExecutionStarted: CATEGORIES.WORKFLOW, + WorkflowExecutionTerminated: CATEGORIES.WORKFLOW, + WorkflowExecutionTimedOut: CATEGORIES.WORKFLOW, + WorkflowExecutionOptionsUpdated: CATEGORIES.WORKFLOW, + WorkflowTaskCompleted: CATEGORIES.WORKFLOW, + WorkflowTaskFailed: CATEGORIES.WORKFLOW, + WorkflowTaskScheduled: CATEGORIES.WORKFLOW, + WorkflowTaskStarted: CATEGORIES.WORKFLOW, + WorkflowTaskTimedOut: CATEGORIES.WORKFLOW, + ExternalWorkflowExecutionCancelRequested: CATEGORIES.WORKFLOW, + RequestCancelExternalWorkflowExecutionFailed: CATEGORIES.WORKFLOW, + RequestCancelExternalWorkflowExecutionInitiated: CATEGORIES.WORKFLOW, + + WorkflowExecutionUpdateAccepted: CATEGORIES.UPDATE, + WorkflowExecutionUpdateCompleted: CATEGORIES.UPDATE, + WorkflowExecutionUpdateRequested: CATEGORIES.UPDATE, + WorkflowExecutionUpdateRejected: CATEGORIES.UPDATE, + WorkflowExecutionUpdateAdmitted: CATEGORIES.UPDATE, + + NexusOperationScheduled: CATEGORIES.NEXUS, + NexusOperationStarted: CATEGORIES.NEXUS, + NexusOperationCompleted: CATEGORIES.NEXUS, + NexusOperationFailed: CATEGORIES.NEXUS, + NexusOperationCanceled: CATEGORIES.NEXUS, + NexusOperationTimedOut: CATEGORIES.NEXUS, + NexusOperationCancelRequested: CATEGORIES.NEXUS, + NexusOperationCancelRequestCompleted: CATEGORIES.NEXUS, + NexusOperationCancelRequestFailed: CATEGORIES.NEXUS, + + MarkerRecorded: CATEGORIES.OTHER, + UpsertWorkflowSearchAttributes: CATEGORIES.OTHER, + WorkflowPropertiesModified: CATEGORIES.OTHER, +}; export type EventTypeOption = { - label: string; - option: EventTypeCategory | undefined; - color?: string; + label: I18nKey; + value: EventTypeCategory; + description?: I18nKey; }; export const allEventTypeOptions: EventTypeOption[] = [ - { label: 'All', option: undefined }, - { label: 'Activity', option: 'activity', color: '#8B5CF6' }, - { label: 'Child Workflow', option: 'child-workflow', color: '#F59E0B' }, - { label: 'Command', option: 'command', color: '#10B981' }, - { label: 'Marker', option: 'marker', color: '#EC4899' }, - { label: 'Signal', option: 'signal', color: '#DD6B20' }, - { label: 'Timer', option: 'timer', color: '#1D4ED8' }, - { label: 'Workflow', option: 'workflow', color: '#10B981' }, + { + label: 'events.category.activity', + value: CATEGORIES.ACTIVITY, + description: 'events.category.activity-tooltip', + }, + { + label: 'events.category.child-workflow', + value: CATEGORIES.CHILD_WORKFLOW, + description: 'events.category.child-workflow-tooltip', + }, + { + label: 'events.category.local-activity', + value: CATEGORIES.LOCAL_ACTIVITY, + description: 'events.category.local-activity-tooltip', + }, + { + label: 'events.category.signal', + value: CATEGORIES.SIGNAL, + description: 'events.category.signal-tooltip', + }, + { + label: 'events.category.timer', + value: CATEGORIES.TIMER, + description: 'events.category.timer-tooltip', + }, + { + label: 'events.category.update', + value: CATEGORIES.UPDATE, + description: 'events.category.update-tooltip', + }, + { + label: 'events.category.nexus', + value: CATEGORIES.NEXUS, + description: 'events.category.nexus-tooltip', + }, + { + label: 'events.category.workflow', + value: CATEGORIES.WORKFLOW, + description: 'events.category.workflow-tooltip', + }, + { + label: 'events.category.other', + value: CATEGORIES.OTHER, + description: 'events.category.other-tooltip', + }, ]; -const compactEventTypes: (EventTypeCategory | undefined)[] = [ - undefined, - 'activity', - 'signal', - 'timer', +const compactEventTypes: EventTypeCategory[] = [ + CATEGORIES.ACTIVITY, + CATEGORIES.LOCAL_ACTIVITY, + CATEGORIES.CHILD_WORKFLOW, + CATEGORIES.SIGNAL, + CATEGORIES.TIMER, + CATEGORIES.UPDATE, + CATEGORIES.NEXUS, + CATEGORIES.OTHER, ]; + export const compactEventTypeOptions: EventTypeOption[] = - allEventTypeOptions.filter(({ option }) => - compactEventTypes.includes(option), - ); - -const timelineEventTypes: EventTypeCategory[] = [ - 'activity', - 'child-workflow', - 'command', - 'marker', - 'signal', - 'timer', -]; -export const timelineEventTypeOptions: EventTypeOption[] = - allEventTypeOptions.filter(({ option }) => - timelineEventTypes.includes(option), - ); + allEventTypeOptions.filter(({ value }) => compactEventTypes.includes(value)); export const getEventCategory = (eventType: EventType): EventTypeCategory => { - return eventTypeCategorizations[eventType]; + return eventTypeCategorizations?.[eventType] || CATEGORIES.OTHER; }; export const isCategoryType = (value: string): value is EventTypeCategory => { - for (const category of categories) { - if (value === category) return true; + for (const category in CATEGORIES) { + if (value === CATEGORIES[category]) return true; } return false; }; diff --git a/src/lib/models/event-history/get-event-classification.test.ts b/src/lib/models/event-history/get-event-classification.test.ts index c1d496cf2..6003fdf58 100644 --- a/src/lib/models/event-history/get-event-classification.test.ts +++ b/src/lib/models/event-history/get-event-classification.test.ts @@ -1,4 +1,7 @@ import { describe, expect, it } from 'vitest'; + +import type { EventType } from '$lib/types/events'; + import { eventClassifications, getEventClassification, @@ -124,7 +127,7 @@ describe('getEventClassification', () => { it('should return undefined for MarkerRecorded', () => { const eventType: EventType = 'MarkerRecorded'; - expect(getEventClassification(eventType)).toBeUndefined(); + expect(getEventClassification(eventType)).toBe('Unspecified'); }); it('should return "Signaled" for WorkflowExecutionSignaled', () => { @@ -233,8 +236,12 @@ describe('getEventClassification', () => { }); it('should return undefined for UpsertWorkflowSearchAttributes', () => { - expect( - getEventClassification('UpsertWorkflowSearchAttributes'), - ).toBeUndefined(); + expect(getEventClassification('UpsertWorkflowSearchAttributes')).toBe( + 'Unspecified', + ); + }); + + it('should return Unspecified for unknown event type', () => { + expect(getEventClassification(4)).toBe('Unspecified'); }); }); diff --git a/src/lib/models/event-history/get-event-classification.ts b/src/lib/models/event-history/get-event-classification.ts index 0301710bb..23ec14581 100644 --- a/src/lib/models/event-history/get-event-classification.ts +++ b/src/lib/models/event-history/get-event-classification.ts @@ -1,4 +1,6 @@ -export type EventClassification = typeof eventClassifications[number]; +import type { EventType } from '$lib/types/events'; + +export type EventClassification = (typeof eventClassifications)[number]; export const eventClassifications = [ 'Unspecified', @@ -19,11 +21,14 @@ export const eventClassifications = [ ] as const; export const getEventClassification = ( - eventType: EventType, + eventType: EventType | number, ): EventClassification => { + if (typeof eventType === 'number') return 'Unspecified'; if (eventType.includes('RequestCancel')) return 'CancelRequested'; for (const classification of eventClassifications) { if (eventType.includes(classification)) return classification; } + + return 'Unspecified'; }; diff --git a/src/lib/models/event-history/index.ts b/src/lib/models/event-history/index.ts index e2ad01f03..44f2fda6d 100644 --- a/src/lib/models/event-history/index.ts +++ b/src/lib/models/event-history/index.ts @@ -1,22 +1,23 @@ -import { - codecEndpoint, - passAccessToken, -} from '$lib/stores/data-encoder-config'; +import type { + EventAttributeKey, + EventAttributesWithType, + EventWithMetadata, + HistoryEvent, + WorkflowEvent, + WorkflowEvents, +} from '$lib/types/events'; import { convertPayloadToJsonWithCodec, - convertPayloadToJsonWithWebsocket, - decodePayloadAttributes, type DecodeFunctions, + decodePayloadAttributes, } from '$lib/utilities/decode-payload'; import { formatDate } from '$lib/utilities/format-date'; +import { isWorkflowTaskFailedEventDueToReset } from '$lib/utilities/get-workflow-task-failed-event'; import { has } from '$lib/utilities/has'; import { findAttributesAndKey } from '$lib/utilities/is-event-type'; -import { - getCodecEndpoint, - getCodecPassAccessToken, -} from '$lib/utilities/get-codec'; +import { toEventNameReadable } from '$lib/utilities/screaming-enums'; -import { groupEvents } from '../event-groups'; +import { getEventBillableActions } from './get-event-billable-actions'; import { getEventCategory } from './get-event-categorization'; import { getEventClassification } from './get-event-classification'; import { simplifyAttributes } from './simplify-attributes'; @@ -25,32 +26,16 @@ export async function getEventAttributes( { historyEvent, namespace, settings, accessToken }: EventWithMetadata, { convertWithCodec = convertPayloadToJsonWithCodec, - convertWithWebsocket = convertPayloadToJsonWithWebsocket, decodeAttributes = decodePayloadAttributes, - encoderEndpoint = codecEndpoint, - codecPassAccessToken = passAccessToken, }: DecodeFunctions = {}, -): Promise { +): Promise> { const { key, attributes } = findAttributesAndKey(historyEvent); - // Use locally set endpoint over settings endpoint for testing purposes - const endpoint = getCodecEndpoint(settings, encoderEndpoint); - const passAccessToken = getCodecPassAccessToken( + const convertedAttributes = await convertWithCodec({ + attributes, + namespace, settings, - codecPassAccessToken, - ); - const _settings = { - ...settings, - codec: { ...settings?.codec, endpoint, passAccessToken }, - }; - - const convertedAttributes = endpoint - ? await convertWithCodec({ - attributes, - namespace, - settings: _settings, - accessToken, - }) - : await convertWithWebsocket(attributes); + accessToken, + }); const decodedAttributes = decodeAttributes(convertedAttributes) as object; @@ -60,54 +45,64 @@ export async function getEventAttributes( }; } -const toEvent = async ({ - historyEvent, - namespace, - settings, - accessToken, -}: EventWithMetadata): Promise => { +export const toBillableEvent = ( + event: WorkflowEvent, + shouldNotAddBillableAction: (event: WorkflowEvent) => boolean = () => false, + processedWorkflowTaskIds?: Set, +) => { + return { + ...event, + billableActions: shouldNotAddBillableAction(event) + ? 0 + : getEventBillableActions(event, processedWorkflowTaskIds), + }; +}; + +export const toEvent = ( + historyEvent: HistoryEvent, + options: { + shouldNotAddBillableAction?: (event: WorkflowEvent) => boolean; + processedWorkflowTaskIds?: Set; + } = {}, +): WorkflowEvent => { const id = String(historyEvent.eventId); - const eventType = historyEvent.eventType as unknown as EventType; + const eventType = toEventNameReadable(historyEvent.eventType); const timestamp = formatDate(String(historyEvent.eventTime)); const classification = getEventClassification(eventType); const category = getEventCategory(eventType); - const attributes = await getEventAttributes({ - historyEvent, - namespace, - settings, - accessToken, - }).then((attributes) => simplifyAttributes(attributes)); - return { + const { key, attributes } = findAttributesAndKey(historyEvent); + const links = historyEvent?.links || []; + const event = { ...historyEvent, - attributes, + name: eventType, + id, eventType, + timestamp, classification, category, - id, - name: eventType, - timestamp, + links, + billableActions: 0, + attributes: simplifyAttributes({ type: key, ...attributes }), }; -}; - -export const toEventHistory = async ({ - response, - namespace, - settings, - accessToken, -}: EventsWithMetadata): Promise<{ - events: WorkflowEvents; - eventGroups: EventGroups; -}> => { - const events = await Promise.all( - response.map((historyEvent) => - toEvent({ historyEvent, namespace, settings, accessToken }), - ), + return toBillableEvent( + event, + options.shouldNotAddBillableAction, + options.processedWorkflowTaskIds, ); +}; - const eventGroups = groupEvents(events); +export const toEventHistory = (events: HistoryEvent[]): WorkflowEvents => { + const failedEvent = events.findLast(isWorkflowTaskFailedEventDueToReset); + const shouldNotAddBillableAction = (event: WorkflowEvent): boolean => { + if (failedEvent) return Number(event.id) < Number(failedEvent.eventId); + return false; + }; - return { events, eventGroups }; + const processedWorkflowTaskIds = new Set(); + return events.map((event) => + toEvent(event, { shouldNotAddBillableAction, processedWorkflowTaskIds }), + ); }; export const isEvent = (event: unknown): event is WorkflowEvent => { @@ -117,3 +112,14 @@ export const isEvent = (event: unknown): event is WorkflowEvent => { if (has(event, 'eventType')) return true; return false; }; + +export const fromEventToRawEvent = (event: WorkflowEvent): HistoryEvent => { + const workflowEvent = { ...event }; + delete workflowEvent.name; + delete workflowEvent.id; + delete workflowEvent.timestamp; + delete workflowEvent.classification; + delete workflowEvent.category; + delete workflowEvent.attributes; + return workflowEvent; +}; diff --git a/src/lib/models/event-history/is-event.test.ts b/src/lib/models/event-history/is-event.test.ts index 870df3a67..3133854e4 100644 --- a/src/lib/models/event-history/is-event.test.ts +++ b/src/lib/models/event-history/is-event.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { isEvent } from '.'; describe('isEvent', () => { diff --git a/src/lib/models/event-history/simplify-attributes.test.ts b/src/lib/models/event-history/simplify-attributes.test.ts index 0efbd0f68..95de7a483 100644 --- a/src/lib/models/event-history/simplify-attributes.test.ts +++ b/src/lib/models/event-history/simplify-attributes.test.ts @@ -1,4 +1,8 @@ import { describe, expect, it } from 'vitest'; + +import type { VersioningBehavior } from '$lib/types'; +import type { EventAttributesWithType } from '$lib/types/events'; + import { canBeSimplified, getValueForFirstKey, @@ -125,3 +129,63 @@ describe('canBeSimplified', () => { expect(canBeSimplified(false)).toBe(false); }); }); + +describe('delete workerVersion if deploymentVersion exists', () => { + it('should return only deploymentVersion if both exist', () => { + const attributes = { + type: 'workflowTaskCompletedEventAttributes', + workerVersion: { + buildId: 'v46', + useVersioning: true, + }, + deploymentVersion: { + deploymentName: 'deployment-v1', + buildId: 'build-123', + }, + } as EventAttributesWithType<'workflowTaskCompletedEventAttributes'>; + expect(simplifyAttributes(attributes)).toStrictEqual({ + type: 'workflowTaskCompletedEventAttributes', + deploymentVersion: { + deploymentName: 'deployment-v1', + buildId: 'build-123', + }, + }); + }); + + it('should return workerVersion if deploymentVersion does not exist', () => { + const attributes = { + type: 'workflowTaskCompletedEventAttributes', + workerVersion: { + buildId: 'v46', + useVersioning: true, + }, + } as EventAttributesWithType<'workflowTaskCompletedEventAttributes'>; + expect(simplifyAttributes(attributes)).toStrictEqual(attributes); + }); +}); + +describe('fromScreamingEnum versioning behavior', () => { + it('should return readable autoupgrade behavior from screaming enum', () => { + const attributes = { + type: 'workflowTaskCompletedEventAttributes', + versioningBehavior: + 'VERSIONING_BEHAVIOR_AUTO_UPGRADE' as unknown as VersioningBehavior, + } as EventAttributesWithType<'workflowTaskCompletedEventAttributes'>; + expect(simplifyAttributes(attributes)).toStrictEqual({ + type: 'workflowTaskCompletedEventAttributes', + versioningBehavior: 'AutoUpgrade', + }); + }); + + it('should return readable pinned behavior from screaming enum', () => { + const attributes = { + type: 'workflowTaskCompletedEventAttributes', + versioningBehavior: + 'VERSIONING_BEHAVIOR_PINNED' as unknown as VersioningBehavior, + } as EventAttributesWithType<'workflowTaskCompletedEventAttributes'>; + expect(simplifyAttributes(attributes)).toStrictEqual({ + type: 'workflowTaskCompletedEventAttributes', + versioningBehavior: 'Pinned', + }); + }); +}); diff --git a/src/lib/models/event-history/simplify-attributes.ts b/src/lib/models/event-history/simplify-attributes.ts index 2e053bb7f..544a444ca 100644 --- a/src/lib/models/event-history/simplify-attributes.ts +++ b/src/lib/models/event-history/simplify-attributes.ts @@ -1,7 +1,14 @@ +import type { PendingActivityInfo, PendingNexusInfo } from '$lib/types'; +import type { + EventAttributeKey, + EventAttributesWithType, + PendingActivity, + PendingNexusOperation, +} from '$lib/types/events'; import { formatDate } from '$lib/utilities/format-date'; import { formatDuration } from '$lib/utilities/format-time'; - -import type { PendingActivityInfo } from '$types'; +import { has } from '$lib/utilities/has'; +import { fromScreamingEnum } from '$lib/utilities/screaming-enums'; const keysToBeFormattedAsTime = [ 'closeTime', @@ -42,7 +49,9 @@ const keysToBeFormattedAsDuration = [ 'workflowTaskTimeout', ] as const; -const isTime = (key: string): key is typeof keysToBeFormattedAsTime[number] => { +const isTime = ( + key: string, +): key is (typeof keysToBeFormattedAsTime)[number] => { for (const timeKey of keysToBeFormattedAsTime) { if (timeKey === key) return true; } @@ -51,7 +60,7 @@ const isTime = (key: string): key is typeof keysToBeFormattedAsTime[number] => { const isDuration = ( key: string, -): key is typeof keysToBeFormattedAsDuration[number] => { +): key is (typeof keysToBeFormattedAsDuration)[number] => { for (const timeKey of keysToBeFormattedAsDuration) { if (timeKey === key) return true; } @@ -81,15 +90,19 @@ export const getValueForFirstKey = (value: Record): string => { }; export function simplifyAttributes( - attributes: EventAttributesWithType, + attributes: EventAttributesWithType, preserveTimestamps?: boolean, -): EventAttributesWithType; +): EventAttributesWithType; export function simplifyAttributes( attributes: PendingActivityInfo, preserveTimestamps?: boolean, -): PendingActivityInfo; +): PendingActivity; +export function simplifyAttributes( + attributes: PendingNexusInfo, + preserveTimestamps?: boolean, +): PendingNexusOperation; export function simplifyAttributes< - T = EventAttributesWithType | PendingActivityInfo, + T = EventAttributesWithType | PendingActivityInfo, >(attributes: T, preserveTimestamps = false): T { for (const [key, value] of Object.entries(attributes)) { if (canBeSimplified(value)) { @@ -103,6 +116,17 @@ export function simplifyAttributes< if (isDuration(key)) { attributes[key] = formatDuration(value); } + + if (key === 'versioningBehavior') { + attributes[key] = fromScreamingEnum(value, 'VersioningBehavior'); + } + } + + if ( + has(attributes, 'workerVersion') && + has(attributes, 'deploymentVersion') + ) { + delete attributes.workerVersion; } return attributes; diff --git a/src/lib/models/event-history/to-event-history.test.ts b/src/lib/models/event-history/to-event-history.test.ts index eceeafe5d..6cbe7e3fb 100644 --- a/src/lib/models/event-history/to-event-history.test.ts +++ b/src/lib/models/event-history/to-event-history.test.ts @@ -1,9 +1,14 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { writable } from 'svelte/store'; -import { getEventAttributes, toEventHistory } from '.'; -import settingsFixture from '$fixtures/settings.json'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import type { HistoryEvent } from '$lib/types/events'; +import type { Settings } from '$lib/types/global'; + +import { fromEventToRawEvent, getEventAttributes, toEventHistory } from '.'; + import eventsFixture from '$fixtures/raw-events.descending.completed.json'; +import settingsFixture from '$fixtures/settings.json'; const historyEvent = { eventId: '1', @@ -86,12 +91,10 @@ describe('getEventAttributes', () => { const fn = async (x: T): Promise => x; const convertPayloadToJsonWithCodec = vi.fn(fn); - const convertPayloadToJsonWithWebsocket = vi.fn(fn); const decodePayloadAttributes = vi.fn(fn); return { convertPayloadToJsonWithCodec, - convertPayloadToJsonWithWebsocket, decodePayloadAttributes, }; }); @@ -112,9 +115,8 @@ describe('getEventAttributes', () => { expect(event.type).toBe(eventType); }); - it('should call convertWithWebSocket if not provided an endpoint', async () => { + it('should call convertWithCodec if not provided an endpoint in settings', async () => { const convertWithCodec = vi.fn(async (x: T): Promise => x); - const convertWithWebsocket = vi.fn(async (x: T): Promise => x); await getEventAttributes( { @@ -124,18 +126,15 @@ describe('getEventAttributes', () => { accessToken, }, { - convertWithWebsocket, convertWithCodec, }, ); - expect(convertWithWebsocket).toBeCalled(); - expect(convertWithCodec).not.toBeCalled(); + expect(convertWithCodec).toBeCalled(); }); it('should call convertWithCodec if provided an endpoint in settings', async () => { const convertWithCodec = vi.fn(async (x: T): Promise => x); - const convertWithWebsocket = vi.fn(async (x: T): Promise => x); await getEventAttributes( { @@ -145,18 +144,15 @@ describe('getEventAttributes', () => { accessToken, }, { - convertWithWebsocket, convertWithCodec, }, ); expect(convertWithCodec).toBeCalled(); - expect(convertWithWebsocket).not.toBeCalled(); }); it('should call convertWithCodec if provided an endpoint in settings', async () => { const convertWithCodec = vi.fn(async (x: T): Promise => x); - const convertWithWebsocket = vi.fn(async (x: T): Promise => x); await getEventAttributes( { @@ -166,14 +162,12 @@ describe('getEventAttributes', () => { accessToken, }, { - convertWithWebsocket, convertWithCodec, encoderEndpoint: writable('https://localhost'), }, ); expect(convertWithCodec).toBeCalled(); - expect(convertWithWebsocket).not.toBeCalled(); }); }); @@ -190,12 +184,9 @@ describe('toEventHistory', () => { for (const property of additionalProperties) { it(`should add a[n] ${property} property`, async () => { - const { events } = await toEventHistory({ - response: eventsFixture.history.events as unknown as HistoryEvent[], - namespace, - settings, - accessToken, - }); + const events = await toEventHistory( + eventsFixture.history.events as unknown as HistoryEvent[], + ); const [event] = events; @@ -203,3 +194,27 @@ describe('toEventHistory', () => { }); } }); + +describe('fromEventToRawEvent', () => { + const additionalProperties = [ + 'attributes', + 'classification', + 'category', + 'id', + 'name', + 'timestamp', + ]; + + for (const property of additionalProperties) { + it(`should remove a[n] ${property} property`, async () => { + const events = await toEventHistory( + eventsFixture.history.events as unknown as HistoryEvent[], + ); + + const [event] = events; + const rawEvent = fromEventToRawEvent(event); + + expect(rawEvent[property]).toBeUndefined(); + }); + } +}); diff --git a/src/lib/models/pending-activities/index.ts b/src/lib/models/pending-activities/index.ts index 5dccdeaa4..d6c80aabd 100644 --- a/src/lib/models/pending-activities/index.ts +++ b/src/lib/models/pending-activities/index.ts @@ -1,33 +1,33 @@ -import { codecEndpoint } from '$lib/stores/data-encoder-config'; +import { get } from 'svelte/store'; + +import { page } from '$app/stores'; + +import { authUser } from '$lib/stores/auth-user'; +import type { + PendingActivity, + PendingActivityWithMetadata, +} from '$lib/types/events'; +import type { Settings } from '$lib/types/global'; +import type { WorkflowExecution } from '$lib/types/workflows'; import { convertPayloadToJsonWithCodec, - convertPayloadToJsonWithWebsocket, - decodePayloadAttributes, type DecodeFunctions, + decodePayloadAttributes, } from '$lib/utilities/decode-payload'; -import { getCodecEndpoint } from '$lib/utilities/get-codec'; export async function getActivityAttributes( { activity, namespace, settings, accessToken }: PendingActivityWithMetadata, { convertWithCodec = convertPayloadToJsonWithCodec, - convertWithWebsocket = convertPayloadToJsonWithWebsocket, decodeAttributes = decodePayloadAttributes, - encoderEndpoint = codecEndpoint, }: DecodeFunctions = {}, ): Promise { - // Use locally set endpoint over settings endpoint for testing purposes - const endpoint = getCodecEndpoint(settings, encoderEndpoint); - const _settings = { ...settings, codec: { ...settings?.codec, endpoint } }; - - const convertedAttributes = endpoint - ? await convertWithCodec({ - attributes: activity, - namespace, - settings: _settings, - accessToken, - }) - : await convertWithWebsocket(activity); + const convertedAttributes = await convertWithCodec({ + attributes: activity, + namespace, + settings, + accessToken, + }); const decodedAttributes = decodeAttributes( convertedAttributes, @@ -52,9 +52,9 @@ const decodePendingActivity = async ({ export const toDecodedPendingActivities = async ( workflow: WorkflowExecution, - namespace: string, - settings: Settings, - accessToken: string, + namespace: string = get(page).params.namespace, + settings: Settings = get(page).data.settings, + accessToken: string = get(authUser).accessToken, ) => { const pendingActivities = workflow?.pendingActivities ?? []; const decodedActivities: PendingActivity[] = []; diff --git a/src/lib/models/pending-activities/to-decoded-pending-activities.test.ts b/src/lib/models/pending-activities/to-decoded-pending-activities.test.ts index 66cb320bc..5d9b324e2 100644 --- a/src/lib/models/pending-activities/to-decoded-pending-activities.test.ts +++ b/src/lib/models/pending-activities/to-decoded-pending-activities.test.ts @@ -1,15 +1,19 @@ import { describe, expect, it } from 'vitest'; +import type { Settings } from '$lib/types/global'; +import type { WorkflowExecution } from '$lib/types/workflows'; + +import { toDecodedPendingActivities } from './index'; + import settingsFixture from '$fixtures/settings.json'; import pendingActivityWorkflow from '$fixtures/workflow.pending-activities.json'; -import { toDecodedPendingActivities } from './index'; const namespace = 'unit-tests'; const settings = settingsFixture as unknown as Settings; const accessToken = 'access-token'; describe('toDecodedPendingActivities', () => { - it(`should decode heartbeatDetails`, async () => { + it('should decode heartbeatDetails', async () => { const workflow = pendingActivityWorkflow as unknown as WorkflowExecution; const decodedHeartbeatDetails = await toDecodedPendingActivities( workflow, diff --git a/src/lib/models/search-attribute-filters.ts b/src/lib/models/search-attribute-filters.ts new file mode 100644 index 000000000..c0de1b6f7 --- /dev/null +++ b/src/lib/models/search-attribute-filters.ts @@ -0,0 +1,14 @@ +import type { + SearchAttributes, + SearchAttributeType, +} from '$lib/types/workflows'; + +export type SearchAttributeFilter = { + attribute: Extract; + type: SearchAttributeType; + value: string; + operator: string; + parenthesis: string; + conditional: string; + customDate?: boolean; +}; diff --git a/src/lib/models/workflow-actions.ts b/src/lib/models/workflow-actions.ts new file mode 100644 index 000000000..6d9dab220 --- /dev/null +++ b/src/lib/models/workflow-actions.ts @@ -0,0 +1,5 @@ +export enum Action { + Cancel, + Reset, + Terminate, +} diff --git a/src/lib/models/workflow-execution.test.ts b/src/lib/models/workflow-execution.test.ts index da7e94055..35564d105 100644 --- a/src/lib/models/workflow-execution.test.ts +++ b/src/lib/models/workflow-execution.test.ts @@ -1,16 +1,22 @@ import { describe, expect, it } from 'vitest'; + +import type { + ListWorkflowExecutionsResponse, + WorkflowExecutionAPIResponse, +} from '$lib/types/workflows'; + import { toWorkflowExecution, toWorkflowExecutions, } from './workflow-execution'; +import listWorkflowsResponse from '$fixtures/list-workflows.json'; import canceledWorkflow from '$fixtures/workflow.canceled.json'; import completedWorkflow from '$fixtures/workflow.completed.json'; import failedWorkflow from '$fixtures/workflow.failed.json'; import runningWorkflow from '$fixtures/workflow.running.json'; import terminatedWorkflow from '$fixtures/workflow.terminated.json'; import timedOutWorkflow from '$fixtures/workflow.timed-out.json'; -import listWorkflowsResponse from '$fixtures/list-workflows.json'; const workflows = [ canceledWorkflow, @@ -19,7 +25,7 @@ const workflows = [ runningWorkflow, terminatedWorkflow, timedOutWorkflow, -]; +] as unknown as WorkflowExecutionAPIResponse[]; describe('toWorkflowExecution', () => { for (const workflow of workflows) { diff --git a/src/lib/models/workflow-execution.ts b/src/lib/models/workflow-execution.ts index 77b636d51..6d660fc9b 100644 --- a/src/lib/models/workflow-execution.ts +++ b/src/lib/models/workflow-execution.ts @@ -1,41 +1,136 @@ +import type { + Callbacks, + PendingActivity, + PendingActivityInfo, + PendingActivityState, + PendingChildren, + PendingNexusOperation, +} from '$lib/types/events'; +import type { Callback } from '$lib/types/nexus'; +import type { + DecodedWorkflowSearchAttributes, + ListWorkflowExecutionsResponse, + WorkflowExecution, + WorkflowExecutionAPIResponse, + WorkflowSearchAttributes, +} from '$lib/types/workflows'; +import { decodePayload } from '$lib/utilities/decode-payload'; +import { + toCallbackStateReadable, + toPendingActivityStateReadable, + toPendingNexusOperationStateReadable, + toWorkflowStatusReadable, +} from '$lib/utilities/screaming-enums'; import { writeActionsAreAllowed } from '$lib/utilities/write-actions-are-allowed'; + import { simplifyAttributes } from './event-history/simplify-attributes'; -const toPendingActivities = ( +export const toPendingActivities = ( pendingActivity: PendingActivityInfo[] = [], ): PendingActivity[] => { - return pendingActivity.map((activity) => { + return pendingActivity.map((activity): PendingActivity => { const attributes = simplifyAttributes(activity, true); const id = activity.activityId; + const state = activity.state as unknown as PendingActivityState; + return { + ...attributes, + id, + state: toPendingActivityStateReadable(state), + }; + }); +}; - return { ...attributes, id } as unknown as PendingActivity; +const toPendingNexusOperations = ( + operations?: PendingNexusOperation[], +): PendingNexusOperation[] => { + if (!operations) return []; + return operations.map((operation): PendingNexusOperation => { + return { + ...operation, + state: toPendingNexusOperationStateReadable(operation.state), + }; }); }; +const toCallbacks = (callbacks?: Callbacks): Callbacks => { + if (!callbacks) return []; + return callbacks.map((callback): Callback => { + return { + ...callback, + state: toCallbackStateReadable(callback.state), + }; + }); +}; + +const toSearchAttributes = ( + apiSearchAttributes: WorkflowSearchAttributes, +): DecodedWorkflowSearchAttributes => { + if (!apiSearchAttributes || !apiSearchAttributes.indexedFields) return {}; + const decoded = Object.entries(apiSearchAttributes.indexedFields).reduce( + (searchAttributes, [searchAttributeName, payload]) => { + return { + ...searchAttributes, + [searchAttributeName]: decodePayload(payload), + }; + }, + {}, + ); + + return { + indexedFields: decoded, + }; +}; + export const toWorkflowExecution = ( response?: WorkflowExecutionAPIResponse, ): WorkflowExecution => { + const searchAttributes = toSearchAttributes( + response.workflowExecutionInfo.searchAttributes, + ); + const memo = response.workflowExecutionInfo.memo; const name = response.workflowExecutionInfo.type.name; const id = response.workflowExecutionInfo.execution.workflowId; const runId = response.workflowExecutionInfo.execution.runId; - const startTime = String(response.workflowExecutionInfo.startTime); - const endTime = String(response.workflowExecutionInfo.closeTime); - const status = response.workflowExecutionInfo.status; - const isRunning = response.workflowExecutionInfo.status === 'Running'; + const startTime = response.workflowExecutionInfo.startTime; + const endTime = response.workflowExecutionInfo.closeTime; + const executionTime = response.workflowExecutionInfo.executionTime; + const status = toWorkflowStatusReadable( + response.workflowExecutionInfo.status, + ); + const isRunning = status === 'Running'; const historyEvents = response.workflowExecutionInfo.historyLength; + const historySizeBytes = response.workflowExecutionInfo.historySizeBytes; const url = `/workflows/${id}/${runId}`; - const taskQueue = response?.executionConfig?.taskQueue?.name; + const taskQueue = + response?.executionConfig?.taskQueue?.name || + response?.workflowExecutionInfo?.taskQueue; + const mostRecentWorkerVersionStamp = + response?.workflowExecutionInfo?.mostRecentWorkerVersionStamp; + const assignedBuildId = response?.workflowExecutionInfo?.assignedBuildId; const parentNamespaceId = response?.workflowExecutionInfo?.parentNamespaceId; const parent = response?.workflowExecutionInfo?.parentExecution; const stateTransitionCount = response.workflowExecutionInfo.stateTransitionCount; const defaultWorkflowTaskTimeout = response.executionConfig?.defaultWorkflowTaskTimeout; - const pendingActivities: PendingActivity[] = toPendingActivities( response.pendingActivities, ); const pendingChildren: PendingChildren[] = response?.pendingChildren ?? []; + const pendingNexusOperations: PendingNexusOperation[] = + toPendingNexusOperations(response?.pendingNexusOperations); + const pendingWorkflowTask = response?.pendingWorkflowTask; + const callbacks = toCallbacks(response?.callbacks); + const rootExecution = response.workflowExecutionInfo?.rootExecution; + const versioningInfo = response.workflowExecutionInfo?.versioningInfo; + const workflowExtendedInfo = response.workflowExtendedInfo ?? {}; + + let summary; + let details; + if (response?.executionConfig?.userMetadata) { + summary = response?.executionConfig?.userMetadata?.summary; + details = response?.executionConfig?.userMetadata?.details; + } return { name, @@ -43,17 +138,31 @@ export const toWorkflowExecution = ( runId, startTime, endTime, + executionTime, status, historyEvents, + historySizeBytes, + searchAttributes, + memo, + rootExecution, url, taskQueue, + assignedBuildId, + mostRecentWorkerVersionStamp, pendingActivities, pendingChildren, + pendingNexusOperations, + pendingWorkflowTask, + callbacks, + versioningInfo, + summary, + details, parentNamespaceId, parent, stateTransitionCount, isRunning, defaultWorkflowTaskTimeout, + workflowExtendedInfo, get canBeTerminated(): boolean { return isRunning && writeActionsAreAllowed(); }, diff --git a/src/lib/models/workflow-execution.write-disabled.test.ts b/src/lib/models/workflow-execution.write-disabled.test.ts index cce6c1440..4f298da8b 100644 --- a/src/lib/models/workflow-execution.write-disabled.test.ts +++ b/src/lib/models/workflow-execution.write-disabled.test.ts @@ -1,16 +1,22 @@ import { describe, expect, it, vi } from 'vitest'; + +import type { + ListWorkflowExecutionsResponse, + WorkflowExecutionAPIResponse, +} from '$lib/types/workflows'; + import { toWorkflowExecution, toWorkflowExecutions, } from './workflow-execution'; +import listWorkflowsResponse from '$fixtures/list-workflows.json'; import canceledWorkflow from '$fixtures/workflow.canceled.json'; import completedWorkflow from '$fixtures/workflow.completed.json'; import failedWorkflow from '$fixtures/workflow.failed.json'; import runningWorkflow from '$fixtures/workflow.running.json'; import terminatedWorkflow from '$fixtures/workflow.terminated.json'; import timedOutWorkflow from '$fixtures/workflow.timed-out.json'; -import listWorkflowsResponse from '$fixtures/list-workflows.json'; vi.mock('$lib/utilities/write-actions-are-allowed', () => { return { @@ -25,7 +31,7 @@ const workflows = [ runningWorkflow, terminatedWorkflow, timedOutWorkflow, -]; +] as unknown as WorkflowExecutionAPIResponse[]; describe('toWorkflowExecution', () => { for (const workflow of workflows) { diff --git a/src/lib/models/workflow-filters.ts b/src/lib/models/workflow-filters.ts index 0a208ef1c..cb3c4644f 100644 --- a/src/lib/models/workflow-filters.ts +++ b/src/lib/models/workflow-filters.ts @@ -1,10 +1,51 @@ -export type WorkflowFilter = { - attribute: keyof SearchAttributes; - value: string; - operator: string; - parenthesis: string; - conditional: string; - customDate?: boolean; +import type { SearchAttributes, WorkflowExecution } from '$lib/types/workflows'; + +export type TextFilterAttributes = + | 'WorkflowId' + | 'WorkflowType' + | 'RunId' + | 'TemporalWorkerDeployment' + | 'TemporalWorkerDeploymentVersion' + | 'TemporalWorkerBuildId' + | 'TemporalWorkflowVersioningBehavior'; +export type TextFilterKeys = + | Extract + | 'deployment' + | 'deploymentVersion' + | 'buildId' + | 'versioningBehavior'; + +export const attributeToHumanReadable: Record = { + WorkflowId: 'Workflow ID', + WorkflowType: 'Type', + RunId: 'Run ID', + TemporalWorkerDeployment: 'Deployment', + TemporalWorkerDeploymentVersion: 'Deployment Version', + TemporalWorkerBuildId: 'Build ID', + TemporalWorkflowVersioningBehavior: 'Versioning Behavior', +}; + +export const attributeToId: Record = { + WorkflowId: 'workflow-id', + WorkflowType: 'workflow-type', + RunId: 'run-id', + TemporalWorkerDeployment: 'deployment', + TemporalWorkerDeploymentVersion: 'deployment-version', + TemporalWorkerBuildId: 'worker-build-id', + TemporalWorkflowVersioningBehavior: 'workflow-versioning-behavior', +}; + +export const searchAttributeToWorkflowKey: Record< + TextFilterAttributes, + TextFilterKeys +> = { + WorkflowId: 'id', + WorkflowType: 'name', + RunId: 'runId', + TemporalWorkerDeployment: 'deployment', + TemporalWorkerDeploymentVersion: 'deploymentVersion', + TemporalWorkerBuildId: 'buildId', + TemporalWorkflowVersioningBehavior: 'versioningBehavior', }; export type SortOrder = 'asc' | 'desc'; diff --git a/src/lib/models/workflow-status.ts b/src/lib/models/workflow-status.ts new file mode 100644 index 000000000..1e207f071 --- /dev/null +++ b/src/lib/models/workflow-status.ts @@ -0,0 +1,22 @@ +import type { WorkflowStatus } from '$lib/types/workflows'; + +export type WorkflowFilters = readonly (WorkflowStatus | 'All')[]; + +export const workflowStatuses: Readonly = [ + 'Running', + 'TimedOut', + 'Completed', + 'Failed', + 'ContinuedAsNew', + 'Canceled', + 'Terminated', +] as const; + +export function isWorkflowStatusType(value: string): value is WorkflowStatus { + return workflowStatuses.includes(value as WorkflowStatus); +} + +export const workflowStatusFilters: WorkflowFilters = [ + 'All', + ...workflowStatuses, +] as const; diff --git a/src/lib/pages/batch-operation.svelte b/src/lib/pages/batch-operation.svelte new file mode 100644 index 000000000..03ac0c318 --- /dev/null +++ b/src/lib/pages/batch-operation.svelte @@ -0,0 +1,60 @@ + + +
+
+ + {translate('batch.back-link')} + +
+ {#key fetchKey} + {#await fetchBatchOperation() then operation} + + + + + + + + {/await} + {/key} +
diff --git a/src/lib/pages/batch-operations.svelte b/src/lib/pages/batch-operations.svelte new file mode 100644 index 000000000..da7fa60c8 --- /dev/null +++ b/src/lib/pages/batch-operations.svelte @@ -0,0 +1,26 @@ + + +

{translate('batch.list-page-title')}

+{#await listBatchOperations(namespace)} + +{:then { operations }} + {#if $inProgressBatchOperation} + + {translate('batch.max-concurrent-alert-description')} + + {/if} + +{/await} diff --git a/src/lib/pages/deployment.svelte b/src/lib/pages/deployment.svelte new file mode 100644 index 000000000..7d549f71e --- /dev/null +++ b/src/lib/pages/deployment.svelte @@ -0,0 +1,90 @@ + + +
+
+
+ + {translate('deployments.back-to-deployments')} + + + {deploymentName} +
+
+

{deploymentName}

+ +
+
+
+
+ {#await deploymentFetch} + + {:then deployment} + translate('common.go-to-page', { page })} + items={deployment.workerDeploymentInfo.versionSummaries} + let:visibleItems + > + + {translate('deployments.deployments')} + + + {#each columns as { label }} + {label} + {/each} + + {#each visibleItems as version} + + {/each} + + {/await} +
diff --git a/src/lib/pages/deployments.svelte b/src/lib/pages/deployments.svelte new file mode 100644 index 000000000..217eab4b5 --- /dev/null +++ b/src/lib/pages/deployments.svelte @@ -0,0 +1,95 @@ + + +
+ {#key [namespace]} + + {translate('deployments.deployments')} +
+
+

+ {translate('deployments.worker-deployments')} +

+ Public Preview +
+
+ + {#each columns as { label }} + {label} + {/each} + + {#each visibleItems as deployment} + + {/each} + + + +

+ Enable Worker Deployments to manage your workers more effectively. Learn more. +

+ {#if error} + + {error} + + {/if} +
+
+
+ {/key} +
diff --git a/src/lib/pages/import-events-view.svelte b/src/lib/pages/import-events-view.svelte new file mode 100644 index 000000000..3b953a6a3 --- /dev/null +++ b/src/lib/pages/import-events-view.svelte @@ -0,0 +1,47 @@ + + +
+ + + +
diff --git a/src/lib/pages/import-events.svelte b/src/lib/pages/import-events.svelte new file mode 100644 index 000000000..10fb32366 --- /dev/null +++ b/src/lib/pages/import-events.svelte @@ -0,0 +1,54 @@ + + +
+ +
+ type HistoryEvent = temporal.api.history.v1.IHistoryEvent +
+ {translate('events.api-history-link')} +
+

+ {translate('events.history-expected-formats')} +

+
+ + +
+
+
diff --git a/src/lib/pages/nexus-create-endpoint.svelte b/src/lib/pages/nexus-create-endpoint.svelte new file mode 100644 index 000000000..2dd904dfb --- /dev/null +++ b/src/lib/pages/nexus-create-endpoint.svelte @@ -0,0 +1,58 @@ + + +
+
+ + {translate('nexus.back-to-endpoints')} + +
+

+ {translate('nexus.create-endpoint')} +

+ +
+ + +
+
diff --git a/src/lib/pages/nexus-edit-endpoint.svelte b/src/lib/pages/nexus-edit-endpoint.svelte new file mode 100644 index 000000000..5ae498ce1 --- /dev/null +++ b/src/lib/pages/nexus-edit-endpoint.svelte @@ -0,0 +1,111 @@ + + +
+
+ + {translate('nexus.back-to-endpoint')} + +
+
+

+ {endpoint.spec.name} +

+
+ +
+
+ + +
+ + +
+
+ (deleteConfirmationModalOpen = false)} + confirmDisabled={confirmDeleteInput !== endpoint.spec.name} +> +

{translate('nexus.delete-modal-title')}

+
+

+ {translate('nexus.delete-modal-confirmation-preface')} + {endpoint.spec.name}? + {translate('nexus.delete-modal-confirmation-postface')} +

+

+ {translate('nexus.type-confirm-preface')} + {endpoint.spec.name} + {translate('nexus.type-confirm-postface')} +

+ +
+
diff --git a/src/lib/pages/nexus-empty-state.svelte b/src/lib/pages/nexus-empty-state.svelte new file mode 100644 index 000000000..e7dcb0562 --- /dev/null +++ b/src/lib/pages/nexus-empty-state.svelte @@ -0,0 +1,75 @@ + + +
+
+
+
+

+ {translate('nexus.endpoints')} +

+
+
+

Get Started

+ +

+ Temporal Nexus allows you to reliably connect Temporal Applications. It promotes a more + modular architecture for sharing a subset of your team's capabilities with + well-defined microservice contracts for other teams to use. Nexus was designed + with Durable Execution in mind and enables each team to have their own + Namespace for improved modularity, security, debugging, and fault isolation. +

+

+ Nexus Services are exposed from a Nexus Endpoint created in the Nexus Registry. Adding a Nexus Endpoint to the Nexus Registry deploys the Endpoint, + so it is available at runtime to serve Nexus requests. +

+

+ A Nexus Endpoint is a reverse proxy that decouples the caller from the + handler and routes requests to upstream targets. It currently supports + routing to a single target Namespace and Task Queue. Nexus Services + and Nexus Operations are often registered in the same Worker as the underlying Temporal primitives + they abstract. +

+ + +
+
+
+ Andromeda +
+
+
+ + diff --git a/src/lib/pages/nexus-endpoint.svelte b/src/lib/pages/nexus-endpoint.svelte new file mode 100644 index 000000000..e36d5cfa4 --- /dev/null +++ b/src/lib/pages/nexus-endpoint.svelte @@ -0,0 +1,75 @@ + + +
+
+ + {translate('nexus.back-to-endpoints')} + +
+
+
+

+ {endpoint.spec.name} +

+ +
+

UUID: {endpoint.id}

+
+
+

Target

+
+
+ Namespace + + {endpoint.spec.target.worker.namespace} + +
+
+ Task Queue + + {endpoint.spec.target.worker.taskQueue} + +
+
+
+
+

Description

+ +
+ {#if endpoint.spec?.allowedCallerNamespaces} +

Allowed Caller Namespaces

+
+ {#each endpoint.spec?.allowedCallerNamespaces as namespace} + {namespace} + {/each} +
+ {/if} +
diff --git a/src/lib/pages/nexus-endpoints.svelte b/src/lib/pages/nexus-endpoints.svelte new file mode 100644 index 000000000..83adb80ae --- /dev/null +++ b/src/lib/pages/nexus-endpoints.svelte @@ -0,0 +1,117 @@ + + +{#if !endpoints?.length && !searchParam} + + + +{:else} +
+

+ {translate('nexus.endpoints')} +

+ +
+
+
+ +
+ {#if endpoints.length} +
+ {#each endpoints as endpoint} + +
+

+ {endpoint.spec.name} +

+ {#if endpoint.lastModifiedTime} +

+ Last update {formatDate( + endpoint.lastModifiedTime, + $timeFormat, + )} +

+ {/if} + {#if endpoint.createdTime} +

+ Created on {formatDate(endpoint.createdTime, $timeFormat)} +

+ {/if} + {#if endpoint.spec?.allowedCallerNamespaces} + {endpoint.spec?.allowedCallerNamespaces.length} + {pluralize( + translate('namespaces.namespace'), + endpoint.spec?.allowedCallerNamespaces.length, + )} + {/if} +
+ + {/each} +
+ {:else} +
+ +
+ {/if} +
+{/if} diff --git a/src/lib/pages/nexus-form.svelte b/src/lib/pages/nexus-form.svelte new file mode 100644 index 000000000..fb8aa9ec6 --- /dev/null +++ b/src/lib/pages/nexus-form.svelte @@ -0,0 +1,187 @@ + + + + +
+ +
+

{translate('nexus.target')}

+

+ {translate('nexus.target-description')} +

+
+ + + +
+

{translate('nexus.access-policy')}

+

+ {translate('nexus.allowed-caller-namespaces-description')} +

+
+ +
+ + + (showPreview = false)} + >{translate('common.description')} + (showPreview = true)} + >{translate('common.preview')} + +
+ {#if showPreview} + + {:else} + (description = event.detail.value)} + /> + {/if} +

Do not include sensitive data.

+
+ +
+ diff --git a/src/lib/pages/schedule-edit.svelte b/src/lib/pages/schedule-edit.svelte index cdfcfb569..7960bdbdd 100644 --- a/src/lib/pages/schedule-edit.svelte +++ b/src/lib/pages/schedule-edit.svelte @@ -1,12 +1,19 @@ {#await scheduleFetch} - -{:then { schedule }} - + +{:then { schedule, searchAttributes }} + {/await} diff --git a/src/lib/pages/schedule-view.svelte b/src/lib/pages/schedule-view.svelte index 3a6d5be69..01afc952a 100644 --- a/src/lib/pages/schedule-view.svelte +++ b/src/lib/pages/schedule-view.svelte @@ -1,39 +1,61 @@ {#await scheduleFetch} +
+
+ + {translate('schedules.back-to-schedules')} + +

+ {scheduleId} +

+
+
{:then schedule} {#if $loading} - + {:else} -
-
- - Back to Schedules - -
-

- -

- {scheduleId} -

-

-
-
-

- {namespace} -

-
+
+
+ + {translate('schedules.back-to-schedules')} + +

+ + {scheduleId} + +

+
+

{schedule?.schedule?.action?.startWorkflow?.workflowType?.name}

-
-

Created: {formatDate(schedule?.info?.createTime, $timeFormat)}

-
+

+ {translate('common.created', { + created: formatDate(schedule?.info?.createTime, $timeFormat, { + relative: $relativeTime, + }), + })} +

{#if schedule?.info?.updateTime} -
-

- Last updated: {formatDate( - schedule?.info?.updateTime, - $timeFormat, - )} -

-
+

+ {translate('common.last-updated', { + updated: formatDate(schedule?.info?.updateTime, $timeFormat, { + relative: $relativeTime, + }), + })} +

{/if} -
+ (showPauseConfirmation = !showPauseConfirmation)} + on:click={() => (pauseConfirmationModalOpen = true)} > - {#each options as option} - - {/each} + (triggerConfirmationModalOpen = true)} + > + {translate('schedules.trigger')} + + (backfillConfirmationModalOpen = true)} + > + {translate('schedules.backfill')} + + + {translate('common.edit')} + + (deleteConfirmationModalOpen = true)} + > + {translate('common.delete')} +
+
{#if schedule?.info?.invalidScheduleError}
{/if} -
- +
+
+ {$workflowCount.count.toLocaleString()} + + + +
+
-
-
+
+
-
-
+ + +
+
+ +
-
-
- -
-
-
(showPauseConfirmation = false)} on:confirmModal={() => handlePause(schedule)} + on:cancelModal={resetReason} >

- {schedule.schedule.state.paused ? 'Unpause' : 'Pause'} Schedule? + {schedule?.schedule.state.paused + ? translate('schedules.unpause-modal-title') + : translate('schedules.pause-modal-title')}

- Are you sure you want to {schedule.schedule.state.paused - ? 'unpause' - : 'pause'} - {scheduleId}? + {schedule?.schedule.state.paused + ? translate('schedules.unpause-modal-confirmation', { + schedule: scheduleId, + }) + : translate('schedules.pause-modal-confirmation', { + schedule: scheduleId, + })}

- Enter a reason for {schedule.schedule.state.paused - ? 'unpausing' - : 'pausing'} the schedule. + {schedule?.schedule.state.paused + ? translate('schedules.unpause-reason') + : translate('schedules.pause-reason')}

-
handleTriggerImmediately()} + on:cancelModal={closeTriggerModal} + > +

+ {translate('schedules.trigger-modal-title')} +

+
+ + {#each policies as policy} + + {/each} + +
+
+ handleBackfill()} + on:cancelModal={closeBackfillModal} + > +

+ {translate('schedules.schedule')} + {translate('schedules.backfill')} +

+
+
+ + + + +
+
+
+ + + +
+ + {#each policies.slice(0, viewMoreBackfillOptions ? policies.length : 3) as policy} + + {/each} + + {#if !viewMoreBackfillOptions} + + {/if} +
+
+
+ (showDeleteConfirmation = false)} - on:confirmModal={() => handleDelete()} + confirmText={translate('common.delete')} + cancelText={translate('common.cancel')} + on:confirmModal={handleDelete} + on:cancelModal={resetReason} > -

Delete Schedule?

+

{translate('schedules.delete-modal-title')}

- Are you sure you want to delete - {scheduleId}? + {translate('schedules.delete-modal-confirmation', { + schedule: scheduleId, + })}

{/if} +{:catch error} +
+
+ + {translate('schedules.back-to-schedules')} + +

+ {scheduleId} +

+

+ {namespace} +

+
+
+ {/await} - - diff --git a/src/lib/pages/schedules-create.svelte b/src/lib/pages/schedules-create.svelte index 855dd0640..dd6eae7c9 100644 --- a/src/lib/pages/schedules-create.svelte +++ b/src/lib/pages/schedules-create.svelte @@ -1,18 +1,30 @@ -
-
-

- SchedulesBeta - -

-

- {namespace} -

-
- -
+ {translate('common.schedules')} -{#await fetchSchedules} - -{:then { schedules, error }} - {#if schedules?.length} - - -
- + {@const showActions = visibleItems.length || query} +

+ +

+
+ {#if showActions} + { + refresh = Date.now(); + }} /> -
- - - - ($timeFormat = 'relative')} - >Relative - ($timeFormat = 'UTC')}>UTC - ($timeFormat = 'local')}>Local - - - - {#each visibleItems as schedule} - - {:else} - - - - - - - - {/each} - - - {:else} -
- - + {#if !createDisabled} + + {/if} + {/if} +
- {/if} -{/await} + + + {#each columns as { label }} + {label} + {/each} + + {#each visibleItems as schedule} + + {/each} + + + {#if error} + + + {error} + + + {:else if query} + + {:else} + +

+ {translate('schedules.getting-started-docs-link-preface')} + {translate('schedules.getting-started-docs-link')} + {translate('schedules.getting-started-cli-link-preface')} + Temporal CLI. +

+ {#if !createDisabled} + + {/if} +
+ {/if} +
+ + + + + + +{/key} + + diff --git a/src/lib/pages/start-workflow.svelte b/src/lib/pages/start-workflow.svelte new file mode 100644 index 000000000..103129ac1 --- /dev/null +++ b/src/lib/pages/start-workflow.svelte @@ -0,0 +1,319 @@ + + +
+ + {translate('workflows.back-to-workflows')} + +

+ Start a Workflow +

+ +
+ onInputChange(e, 'workflowId')} + /> + +
+
+ onInputChange(e, 'taskQueue')} + /> +
+ {#if pollerCount !== undefined} + 0 ? 'success' : 'warning'} + title={pollerCount ? 'Task Queue is Active' : 'Task Queue is Inactive'} + > +
+

+ {pollerCount} + {pluralize('Worker', pollerCount)} +

+ + View Task Queue + +
+ {/if} + onInputChange(e, 'workflowType')} + /> + + {#if viewAdvancedOptions} + +
+

{translate('workflows.user-metadata')}

+

+ {translate('workflows.markdown-supported')} + + +

+
+
+ + {/if} +
+ + +
+ {#if error} + + {/if} + +
+
diff --git a/src/lib/pages/task-queue-versioning.svelte b/src/lib/pages/task-queue-versioning.svelte new file mode 100644 index 000000000..e0790188e --- /dev/null +++ b/src/lib/pages/task-queue-versioning.svelte @@ -0,0 +1,49 @@ + + +{#await getVersioning({ namespace, queue: taskQueue })} + +{:then { rules, compatibility, versionSets }} + {#if hasRules(rules)} + + {:else if versioningEnabled && versionSets?.length} + {#await getWorkerTaskReachability( { namespace, buildIds, queue: taskQueue }, )} + + {:then reachability} + + + {/await} + {:else} +

No versioning configured for task queue.

+ {/if} +{/await} diff --git a/src/lib/pages/task-queue-workers.svelte b/src/lib/pages/task-queue-workers.svelte deleted file mode 100644 index 070d81044..000000000 --- a/src/lib/pages/task-queue-workers.svelte +++ /dev/null @@ -1,14 +0,0 @@ - - -{#await workers then workers} - -{/await} diff --git a/src/lib/pages/task-queue.svelte b/src/lib/pages/task-queue.svelte new file mode 100644 index 000000000..e0f5e5634 --- /dev/null +++ b/src/lib/pages/task-queue.svelte @@ -0,0 +1,17 @@ + + +{#await getPollers({ queue: taskQueue, namespace }) then workers} +
+

+ {taskQueue} +

+ +
+{/await} diff --git a/src/lib/pages/workflow-call-stack.svelte b/src/lib/pages/workflow-call-stack.svelte new file mode 100644 index 000000000..ca1070a53 --- /dev/null +++ b/src/lib/pages/workflow-call-stack.svelte @@ -0,0 +1,100 @@ + + +
+ {#if workflow?.isRunning && workers?.pollers?.length > 0} + {#await stackTrace} +
+ + + +
+ {:then result} + +

+ {translate('workflows.call-stack-at')} + {refreshDate} +

+
+ +
+ {:catch _error} + + {/await} + {:else} + + {#if workflow?.isRunning && workers?.pollers?.length === 0} +

+ {translate('workflows.call-stack-link-preface')} + {translate('workflows.call-stack-link')}{translate('workflows.call-stack-link-postface', { + taskQueue: workflow?.taskQueue, + })} +

+ {/if} +
+ {/if} +
diff --git a/src/lib/pages/workflow-history-compact.svelte b/src/lib/pages/workflow-history-compact.svelte deleted file mode 100644 index 0d191b244..000000000 --- a/src/lib/pages/workflow-history-compact.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/src/lib/pages/workflow-history-event.svelte b/src/lib/pages/workflow-history-event.svelte new file mode 100644 index 000000000..82b1a27db --- /dev/null +++ b/src/lib/pages/workflow-history-event.svelte @@ -0,0 +1,147 @@ + + +
+ + + + + {#each visibleItems as event, index} + isEvent(event) && g.eventIds.has(event.id))} + initialItem={$fullEventHistory[0]} + /> + {/each} + +
+ +
+ + diff --git a/src/lib/pages/workflow-history-feed.svelte b/src/lib/pages/workflow-history-feed.svelte deleted file mode 100644 index 799dfa874..000000000 --- a/src/lib/pages/workflow-history-feed.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - - diff --git a/src/lib/pages/workflow-history-json.svelte b/src/lib/pages/workflow-history-json.svelte index 0185c4573..ab7182b33 100644 --- a/src/lib/pages/workflow-history-json.svelte +++ b/src/lib/pages/workflow-history-json.svelte @@ -1,23 +1,16 @@ -{#await events} - -{:then events} - -{/await} + + + diff --git a/src/lib/pages/workflow-history.svelte b/src/lib/pages/workflow-history.svelte index 5fde929ad..8581b8f24 100644 --- a/src/lib/pages/workflow-history.svelte +++ b/src/lib/pages/workflow-history.svelte @@ -1,21 +1,21 @@ + +
+ {#if workflow?.searchAttributes} +
+

+ {translate('events.attribute-group.search-attributes')} +

+ + + +
+ {/if} + + {#if workflow?.memo} +
+

{translate('common.memo')}

+ + + +
+ {/if} +
diff --git a/src/lib/pages/workflow-pending-activities.svelte b/src/lib/pages/workflow-pending-activities.svelte index f822b6bde..0dc45b3c3 100644 --- a/src/lib/pages/workflow-pending-activities.svelte +++ b/src/lib/pages/workflow-pending-activities.svelte @@ -1,160 +1,30 @@ -{#if pendingActivities.length} -
-
-

Activity Id

-

Details

-
- {#each pendingActivities as { id, activityId, ...details } (id)} - {@const failed = details.attempt > 1} -
-
- {activityId} -
-
-
-

Activity Type

- - {details.activityType} - -
-
-

Attempt

- - {#if failed} - - {/if} - {details.attempt} - -
- {#if failed} -
-

Attempts Left

- - {formatAttemptsLeft(details.maximumAttempts, details.attempt)} - -
- {#if details.scheduledTime} -
-

Next Retry

- - {toTimeDifference(details.scheduledTime)} - -
- {/if} - {/if} -
-

Maximum Attempts

- {formatMaximumAttempts(details.maximumAttempts)} -
- {#if failed} - {#if details.heartbeatDetails} -
-

Heartbeat Details

- -
- {/if} - {#if details.lastFailure} -
-

Last Failure

- -
- {/if} -
-

Retry Expiration

-

- {formatRetryExpiration( - details.maximumAttempts, - formatDuration( - getDuration({ - start: Date.now(), - end: details.expirationTime, - }), - ), - )} -

-
- {/if} -
-

Last Heartbeat

-

{formatDate(details.lastHeartbeatTime, 'relative')}

-
-
-

State

-

{details.state}

-
- {#if details.lastStartedTime} -
-

Last Started Time

-

{formatDate(details.lastStartedTime, $timeFormat)}

-
- {/if} - {#if details.scheduledTime} -
-

Scheduled Time

-

{formatDate(details.scheduledTime, $timeFormat)}

-
- {/if} - {#if details.lastWorkerIdentity} -
-

Last Worker Identity

-

{details.lastWorkerIdentity}

-
- {/if} -
-
- {/each} -
-{:else} - -{/if} - - +
+ {#if pendingActivities.length} +
+ {#each pendingActivities as activity (activity.id)} + + {/each} +
+ {:else} + + {/if} +
diff --git a/src/lib/pages/workflow-query.svelte b/src/lib/pages/workflow-query.svelte index 94835f3c8..b49533ea4 100644 --- a/src/lib/pages/workflow-query.svelte +++ b/src/lib/pages/workflow-query.svelte @@ -1,90 +1,205 @@
- {#await queryTypes} + {#if metadataError} + + {:else if !queryTypes.length}
-

(This will fail if you have no workers running.)

+

{translate('workflows.no-workers-failure-message')}

- {:then types} -
- - + {:else} +
+ + +
+ +
+
+ +
+
+
+ {#await Promise.all( [queryResult, encodePayloadResult], ) then [result, _]} + {@const content = + typeof result !== 'string' ? stringifyWithBigInt(result) : result} +
+ (jsonFormatting = !jsonFormatting)} + /> +
+ + {:catch _error} + + {/await} +
-
- {#await queryResult then result} - - {/await} -
- {:catch _error} - - {/await} + {/if}
diff --git a/src/lib/pages/workflow-stack-trace.svelte b/src/lib/pages/workflow-stack-trace.svelte deleted file mode 100644 index b3f0a05da..000000000 --- a/src/lib/pages/workflow-stack-trace.svelte +++ /dev/null @@ -1,91 +0,0 @@ - - -
- {#if workflow.isRunning && workers?.pollers?.length > 0} - {#await stackTrace} -
- -

(This will fail if you have no workers running.)

-
- {:then result} -
- -

Stack Trace at {currentdate.toLocaleTimeString()}

-
-
- -
- {:catch _error} - - {/await} - {:else} - - {#if workflow.isRunning && workers?.pollers?.length === 0} -

- To enable stack traces, run a Worker on the {workflow?.taskQueue} Task Queue. -

- {/if} -
- {/if} -
diff --git a/src/lib/pages/workflow-workers.svelte b/src/lib/pages/workflow-workers.svelte index 1d344a428..7bb3163a7 100644 --- a/src/lib/pages/workflow-workers.svelte +++ b/src/lib/pages/workflow-workers.svelte @@ -1,9 +1,28 @@ - +
+ +

+ {translate('common.task-queue')}: + {taskQueue} +

+
+
diff --git a/src/lib/pages/workflows-with-new-search.svelte b/src/lib/pages/workflows-with-new-search.svelte index 0fd03feb4..cca53888f 100644 --- a/src/lib/pages/workflows-with-new-search.svelte +++ b/src/lib/pages/workflows-with-new-search.svelte @@ -1,122 +1,231 @@ + + -
-
-

- Recent Workflows - -

-
-

- {$page.params.namespace} -

- {#if $workflowCount?.totalCount >= 0} -
-

- {#if $loading} - loading - {:else if $updating} - filtering - {:else if query} - Results {$workflowCount?.count ?? 0} of {$workflowCount?.totalCount ?? - 0} workflows - {:else} - {$workflowCount?.totalCount ?? 0} workflows - {/if} -

+ + + + + + + + + + +
+
+

+ {#if $supportsAdvancedVisibility} + {$workflowCount.count.toLocaleString()} + + {:else} + {/if} -

-
-
- + + + {#if !workflowCreateDisabled($page)} + + {/if}
-
- - - - - - - - - {#each visibleItems as event} - - {:else} - - - - {#if $loading} - - {:else} - - {/if} - - - - {/each} - - + + + + + + + + + diff --git a/src/lib/pages/workflows.svelte b/src/lib/pages/workflows.svelte deleted file mode 100644 index 2655fbbae..000000000 --- a/src/lib/pages/workflows.svelte +++ /dev/null @@ -1,194 +0,0 @@ - - - (showBulkOperationConfirmationModal = false)} - on:confirmModal={terminateWorkflows} -> -

Terminate Workflows

- -

- Are you sure you want to terminate {terminableWorkflows.length} running {pluralize( - 'workflow', - terminableWorkflows.length, - )}? This action cannot be undone. -

- -
-
- -
-
-

- Recent Workflows - -

-
-

- {$page.params.namespace} -

-
-
-
- -
-
- - - - {#each visibleItems as event} - - {:else} - - - - {#if $loading} - - {:else} - - {/if} - - - - {/each} - - diff --git a/src/lib/runes/workflow-versions.svelte.ts b/src/lib/runes/workflow-versions.svelte.ts new file mode 100644 index 000000000..856cd9e9a --- /dev/null +++ b/src/lib/runes/workflow-versions.svelte.ts @@ -0,0 +1,116 @@ +import type { + GetPollersResponse, + PollerWithTaskQueueTypes, +} from '$lib/services/pollers-service'; +import { VersioningBehaviorEnum } from '$lib/types/deployments'; +import type { WorkflowExecution } from '$lib/types/workflows'; +import { getBuildIdFromVersion } from '$lib/utilities/get-deployment-build-id'; + +type PollersWithVersions = { + pollers: PollerWithTaskQueueTypes[]; + pinned: boolean; + autoUpgrade: boolean; + currentDeployment: string; + currentBuildId: string; + rampingDeployment: string; + rampingBuildId: string; +}; + +export function getWorkflowPollersWithVersions( + workflow: WorkflowExecution, + workers: GetPollersResponse, +): PollersWithVersions { + const workflowDeploymentName = $derived( + workflow?.searchAttributes?.indexedFields?.['TemporalWorkerDeployment'], + ); + + const workflowDeploymentVersion = $derived( + workflow?.searchAttributes?.indexedFields?.[ + 'TemporalWorkerDeploymentVersion' + ], + ); + + const workflowVersioningBuildId = $derived( + workflow?.searchAttributes?.indexedFields?.['TemporalWorkerBuildId'] ?? + getBuildIdFromVersion(workflowDeploymentVersion), + ); + + const versioningBehavior = $derived( + workflow?.searchAttributes?.indexedFields?.[ + 'TemporalWorkflowVersioningBehavior' + ], + ); + + const pinnedBehavior = $derived( + versioningBehavior === VersioningBehaviorEnum.Pinned, + ); + const autoUpgradeBehavior = $derived( + versioningBehavior === VersioningBehaviorEnum.AutoUpgrade, + ); + + const currentDeployment = $derived( + workers?.versioningInfo?.currentDeploymentVersion?.deploymentName, + ); + const currentBuildId = $derived( + workers?.versioningInfo?.currentDeploymentVersion?.buildId, + ); + const rampingDeployment = $derived( + workers?.versioningInfo?.rampingDeploymentVersion?.deploymentName, + ); + const rampingBuildId = $derived( + workers?.versioningInfo?.rampingDeploymentVersion?.buildId, + ); + + const getPollerDeploymentName = (poller: PollerWithTaskQueueTypes) => { + const deployment = + poller?.deploymentOptions?.deploymentName ?? + poller?.workerVersionCapabilities?.deploymentSeriesName; + return deployment ?? ''; + }; + + const getPollerBuildId = (poller: PollerWithTaskQueueTypes) => { + const buildId = + poller?.deploymentOptions?.buildId ?? + poller?.workerVersionCapabilities?.buildId; + return buildId ?? ''; + }; + + const pollerHasWorkflowBuildId = $derived( + (poller) => + getPollerDeploymentName(poller) === workflowDeploymentName && + getPollerBuildId(poller) === workflowVersioningBuildId, + ); + + const pollerHasRampingBuildId = $derived( + (poller: PollerWithTaskQueueTypes) => + getPollerDeploymentName(poller) === rampingDeployment && + getPollerBuildId(poller) === rampingBuildId, + ); + + const pollers = $derived.by(() => { + try { + if (pinnedBehavior) { + return workers?.pollers?.filter(pollerHasWorkflowBuildId) ?? []; + } else if (autoUpgradeBehavior) { + return ( + workers?.pollers?.filter( + (p) => pollerHasWorkflowBuildId(p) || pollerHasRampingBuildId(p), + ) ?? [] + ); + } + return workers?.pollers ?? []; + } catch (error) { + return workers?.pollers ?? []; + } + }); + + return { + pollers, + pinned: pinnedBehavior, + autoUpgrade: autoUpgradeBehavior, + currentDeployment, + currentBuildId, + rampingDeployment, + rampingBuildId, + }; +} diff --git a/src/lib/runes/workflow-versions.test.ts b/src/lib/runes/workflow-versions.test.ts new file mode 100644 index 000000000..9aa720481 --- /dev/null +++ b/src/lib/runes/workflow-versions.test.ts @@ -0,0 +1,287 @@ +import { describe, expect, it } from 'vitest'; + +import type { + GetPollersResponse, + PollerWithTaskQueueTypes, +} from '$lib/services/pollers-service'; +import { VersioningBehaviorEnum } from '$lib/types/deployments'; +import type { WorkflowExecution } from '$lib/types/workflows'; + +import { getWorkflowPollersWithVersions } from './workflow-versions.svelte'; // Update with actual file path + +describe('getWorkflowPollersWithVersions', () => { + const createMockWorkflow = ( + deploymentName?: string, + deploymentVersion?: string, + buildId?: string, + versioningBehavior?: string, + ): WorkflowExecution => + ({ + searchAttributes: { + indexedFields: { + ...(deploymentName && { TemporalWorkerDeployment: deploymentName }), + ...(deploymentVersion && { + TemporalWorkerDeploymentVersion: deploymentVersion, + }), + ...(buildId && { TemporalWorkerBuildId: buildId }), + ...(versioningBehavior && { + TemporalWorkflowVersioningBehavior: versioningBehavior, + }), + }, + }, + }) as unknown as WorkflowExecution; + + const createMockPoller = ( + deploymentName?: string, + buildId?: string, + useDeploymentOptions = true, + ): PollerWithTaskQueueTypes => { + if (useDeploymentOptions) { + return { + deploymentOptions: { + deploymentName, + buildId, + }, + } as PollerWithTaskQueueTypes; + } else { + return { + workerVersionCapabilities: { + deploymentSeriesName: deploymentName, + buildId, + }, + } as PollerWithTaskQueueTypes; + } + }; + + const createMockWorkers = ( + pollers: PollerWithTaskQueueTypes[], + currentDeployment?: { deploymentName: string; buildId: string }, + rampingDeployment?: { deploymentName: string; buildId: string }, + ): GetPollersResponse => + ({ + pollers, + versioningInfo: { + currentDeploymentVersion: currentDeployment, + rampingDeploymentVersion: rampingDeployment, + }, + }) as GetPollersResponse; + + describe('Pinned behavior', () => { + it('should filter pollers matching workflow deployment and build ID when pinned', () => { + const workflow = createMockWorkflow( + 'deployment-v1', + 'v1.0.0', + 'build-123', + VersioningBehaviorEnum.Pinned, + ); + + const matchingPoller = createMockPoller('deployment-v1', 'build-123'); + const nonMatchingPoller = createMockPoller('deployment-v2', 'build-456'); + + const workers = createMockWorkers([matchingPoller, nonMatchingPoller], { + deploymentName: 'deployment-v1', + buildId: 'build-123', + }); + + const result = getWorkflowPollersWithVersions(workflow, workers); + + expect(result.pollers).toHaveLength(1); + expect(result.pollers[0]).toBe(matchingPoller); + expect(result.pinned).toBe(true); + expect(result.autoUpgrade).toBe(false); + }); + + it('should handle pollers with workerVersionCapabilities instead of deploymentOptions', () => { + const workflow = createMockWorkflow( + 'deployment-v1', + 'v1.0.0', + 'build-123', + VersioningBehaviorEnum.Pinned, + ); + + const pollerWithCapabilities = createMockPoller( + 'deployment-v1', + 'build-123', + false, + ); + + const workers = createMockWorkers([pollerWithCapabilities], { + deploymentName: 'deployment-v1', + buildId: 'build-123', + }); + + const result = getWorkflowPollersWithVersions(workflow, workers); + + expect(result.pollers).toHaveLength(1); + expect(result.pollers[0]).toBe(pollerWithCapabilities); + }); + }); + + describe('AutoUpgrade behavior', () => { + it('should include pollers matching workflow or ramping deployment when auto-upgrade', () => { + const workflow = createMockWorkflow( + 'deployment-v1', + 'v1.0.0', + 'build-123', + VersioningBehaviorEnum.AutoUpgrade, + ); + + const workflowPoller = createMockPoller('deployment-v1', 'build-123'); + const rampingPoller = createMockPoller('deployment-v2', 'build-456'); + const unrelatedPoller = createMockPoller('deployment-v3', 'build-789'); + + const workers = createMockWorkers( + [workflowPoller, rampingPoller, unrelatedPoller], + { deploymentName: 'deployment-v1', buildId: 'build-123' }, + { deploymentName: 'deployment-v2', buildId: 'build-456' }, + ); + + const result = getWorkflowPollersWithVersions(workflow, workers); + + expect(result.pollers).toHaveLength(2); + expect(result.pollers).toContain(workflowPoller); + expect(result.pollers).toContain(rampingPoller); + expect(result.pollers).not.toContain(unrelatedPoller); + expect(result.autoUpgrade).toBe(true); + expect(result.pinned).toBe(false); + }); + }); + + describe('Default behavior (neither pinned nor auto-upgrade)', () => { + it('should return all pollers when versioning behavior is not set', () => { + const workflow = createMockWorkflow( + 'deployment-v1', + 'v1.0.0', + 'build-123', + ); + + const poller1 = createMockPoller('deployment-v1', 'build-123'); + const poller2 = createMockPoller('deployment-v2', 'build-456'); + + const workers = createMockWorkers([poller1, poller2]); + + const result = getWorkflowPollersWithVersions(workflow, workers); + + expect(result.pollers).toHaveLength(2); + expect(result.pollers).toContain(poller1); + expect(result.pollers).toContain(poller2); + expect(result.pinned).toBe(false); + expect(result.autoUpgrade).toBe(false); + }); + }); + + describe('Edge cases', () => { + it('should handle workflow without search attributes', () => { + const workflow = {} as WorkflowExecution; + const poller = createMockPoller('deployment-v1', 'build-123'); + const workers = createMockWorkers([poller]); + + const result = getWorkflowPollersWithVersions(workflow, workers); + + expect(result.pollers).toHaveLength(1); + expect(result.currentDeployment).toBeUndefined(); + expect(result.currentBuildId).toBeUndefined(); + }); + + it('should handle workers without pollers', () => { + const workflow = createMockWorkflow( + 'deployment-v1', + 'v1.0.0', + 'build-123', + ); + const workers = createMockWorkers([]); + + const result = getWorkflowPollersWithVersions(workflow, workers); + + expect(result.pollers).toHaveLength(0); + }); + + it('should handle workers as undefined', () => { + const workflow = createMockWorkflow( + 'deployment-v1', + 'v1.0.0', + 'build-123', + ); + const workers = undefined as unknown as GetPollersResponse; + + const result = getWorkflowPollersWithVersions(workflow, workers); + + expect(result.pollers).toHaveLength(0); + expect(result.currentDeployment).toBeUndefined(); + expect(result.currentBuildId).toBeUndefined(); + }); + + it('should fallback to getBuildIdFromVersion when TemporalWorkerBuildId is not present', () => { + const workflow = createMockWorkflow( + 'deployment-v1', + 'v1.0.0', + undefined, // No buildId + VersioningBehaviorEnum.Pinned, + ); + const workers = createMockWorkers([]); + + const result = getWorkflowPollersWithVersions(workflow, workers); + + expect(result).toBeDefined(); + }); + + it('should handle errors in pollers filtering gracefully', () => { + const workflow = createMockWorkflow( + 'deployment-v1', + 'v1.0.0', + 'build-123', + VersioningBehaviorEnum.Pinned, + ); + + const problematicPoller = null as unknown as PollerWithTaskQueueTypes; + const normalPoller = createMockPoller('deployment-v1', 'build-123'); + + const workers = createMockWorkers([problematicPoller, normalPoller]); + + expect(() => { + const result = getWorkflowPollersWithVersions(workflow, workers); + expect(result.pollers).toBeDefined(); + }).not.toThrow(); + }); + + it('should handle pollers with missing deployment options or capabilities', () => { + const workflow = createMockWorkflow( + 'deployment-v1', + 'v1.0.0', + 'build-123', + VersioningBehaviorEnum.Pinned, + ); + + const incompletePoller = {} as PollerWithTaskQueueTypes; + + const workers = createMockWorkers([incompletePoller]); + + const result = getWorkflowPollersWithVersions(workflow, workers); + + expect(result.pollers).toHaveLength(0); + }); + }); + + describe('Deployment version info', () => { + it('should correctly extract current and ramping deployment info', () => { + const workflow = createMockWorkflow( + 'deployment-v1', + 'v1.0.0', + 'build-123', + ); + + const workers = createMockWorkers( + [], + { deploymentName: 'current-deployment', buildId: 'v1' }, + { deploymentName: 'ramping-deployment', buildId: 'v2' }, + ); + + const result = getWorkflowPollersWithVersions(workflow, workers); + + expect(result.currentDeployment).toBe('current-deployment'); + expect(result.currentBuildId).toBe('v1'); + expect(result.rampingDeployment).toBe('ramping-deployment'); + expect(result.rampingBuildId).toBe('v2'); + }); + }); +}); diff --git a/src/lib/services/batch-service.test.ts b/src/lib/services/batch-service.test.ts new file mode 100644 index 000000000..0b865f918 --- /dev/null +++ b/src/lib/services/batch-service.test.ts @@ -0,0 +1,137 @@ +import { writable } from 'svelte/store'; + +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + +import { batchCancelWorkflows, batchTerminateWorkflows } from './batch-service'; +import { temporalVersion } from '../stores/versions'; +import { requestFromAPI } from '../utilities/request-from-api'; + +const mockWorkflows = [ + { id: '1', runId: 'a' }, + { id: '2', runId: 'b' }, + { id: '3', runId: 'c' }, +]; + +vi.mock('../utilities/request-from-api', () => ({ + requestFromAPI: vi.fn().mockImplementation((route) => { + if (route.includes('/describe')) { + return new Promise((resolve) => + resolve({ state: 'Completed', completedOperationCount: '10' }), + ); + } + + return new Promise((resolve) => resolve('xxx')); + }), +})); + +vi.mock('../stores/versions', () => { + return { + temporalVersion: writable(), + }; +}); + +describe('Batch Service', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('when temporal server version is <= 1.19', () => { + beforeEach(() => { + vi.spyOn(temporalVersion, 'subscribe').mockImplementation( + (fn) => () => fn('1.19'), + ); + }); + + test('batchTerminateWorkflows calls the create batch operation endpoint with the correct options', async () => { + await batchTerminateWorkflows({ + namespace: 'default', + reason: 'test', + workflows: mockWorkflows, + jobId: 'xxx', + }); + + expect(requestFromAPI).toHaveBeenCalledTimes(2); + expect(requestFromAPI).toHaveBeenCalledWith( + 'http://localhost:8233/api/v1/namespaces/default/batch-operations/xxx', + { + notifyOnError: false, + options: { + method: 'POST', + body: '{"jobId":"xxx","namespace":"default","reason":"test","terminationOperation":{},"visibilityQuery":"RunId=\\"a\\" OR RunId=\\"b\\" OR RunId=\\"c\\""}', + }, + }, + ); + }); + + test('batchCancelWorkflows calls the create batch operation endpoint with the correct options', async () => { + await batchCancelWorkflows({ + namespace: 'default', + reason: 'test', + workflows: mockWorkflows, + jobId: 'xxx', + }); + + expect(requestFromAPI).toHaveBeenCalledTimes(2); + expect(requestFromAPI).toHaveBeenCalledWith( + 'http://localhost:8233/api/v1/namespaces/default/batch-operations/xxx', + { + notifyOnError: false, + options: { + method: 'POST', + body: '{"jobId":"xxx","namespace":"default","reason":"test","cancellationOperation":{},"visibilityQuery":"RunId=\\"a\\" OR RunId=\\"b\\" OR RunId=\\"c\\""}', + }, + }, + ); + }); + }); + + describe('when temporal server version is > 1.19', () => { + beforeEach(() => { + vi.spyOn(temporalVersion, 'subscribe').mockImplementation( + (fn) => () => fn('1.20'), + ); + }); + + test('batchTerminateWorkflows calls the create batch operation endpoint with the correct options', async () => { + await batchTerminateWorkflows({ + namespace: 'default', + reason: 'test', + workflows: mockWorkflows, + jobId: 'xxx', + }); + + expect(requestFromAPI).toHaveBeenCalledTimes(2); + expect(requestFromAPI).toHaveBeenCalledWith( + 'http://localhost:8233/api/v1/namespaces/default/batch-operations/xxx', + { + notifyOnError: false, + options: { + method: 'POST', + body: '{"jobId":"xxx","namespace":"default","reason":"test","terminationOperation":{},"executions":[{"workflowId":"1","runId":"a"},{"workflowId":"2","runId":"b"},{"workflowId":"3","runId":"c"}]}', + }, + }, + ); + }); + + test('batchCancelWorkflows calls the create batch operation endpoint with the correct options', async () => { + await batchCancelWorkflows({ + namespace: 'default', + reason: 'test', + workflows: mockWorkflows, + jobId: 'xxx', + }); + + expect(requestFromAPI).toHaveBeenCalledTimes(2); + expect(requestFromAPI).toHaveBeenCalledWith( + 'http://localhost:8233/api/v1/namespaces/default/batch-operations/xxx', + { + notifyOnError: false, + options: { + method: 'POST', + body: '{"jobId":"xxx","namespace":"default","reason":"test","cancellationOperation":{},"executions":[{"workflowId":"1","runId":"a"},{"workflowId":"2","runId":"b"},{"workflowId":"3","runId":"c"}]}', + }, + }, + ); + }); + }); +}); diff --git a/src/lib/services/batch-service.ts b/src/lib/services/batch-service.ts new file mode 100644 index 000000000..0ea51a466 --- /dev/null +++ b/src/lib/services/batch-service.ts @@ -0,0 +1,309 @@ +import { get } from 'svelte/store'; + +import { Action } from '$lib/models/workflow-actions'; +import { getAuthUser } from '$lib/stores/auth-user'; +import { inProgressBatchOperation } from '$lib/stores/batch-operations'; +import { temporalVersion } from '$lib/stores/versions'; +import { stringifyWithBigInt } from '$lib/utilities/parse-with-big-int'; +import { requestFromAPI } from '$lib/utilities/request-from-api'; +import { routeForApi } from '$lib/utilities/route-for-api'; +import { + toBatchOperationStateReadable, + toBatchOperationTypeReadable, +} from '$lib/utilities/screaming-enums'; +import { isVersionNewer } from '$lib/utilities/version-check'; + +import type { + StartBatchOperationRequest, + WorkflowExecutionInput, +} from '$types'; +import type { + APIBatchOperationInfo, + BatchOperation, + BatchOperationInfo, + BatchOperations, + DescribeBatchOperationResponse, + ListBatchOperationsResponse, +} from '$types/batch'; +import type { WorkflowExecution } from '$types/workflows'; + +// https://github.com/temporalio/api/blob/master/temporal/api/enums/v1/reset.proto +enum ResetType { + RESET_TYPE_FIRST_WORKFLOW_TASK = 1, + RESET_TYPE_LAST_WORKFLOW_TASK = 2, +} + +type CreateBatchOperationOptions = { + namespace: string; + reason: string; + jobId: string; + query?: string; + workflows?: WorkflowExecution[]; + resetType?: 'first' | 'last'; +}; + +type DescribeBatchOperationOptions = { + jobId: string; + namespace: string; +}; + +const queryFromWorkflows = ( + workflowExecutions: WorkflowExecution[], +): string => { + const runIds = workflowExecutions.map((wf) => wf.runId); + return runIds.reduce((queryString, id, index, arr) => { + queryString += `RunId="${id}"`; + if (index !== arr.length - 1) { + queryString += ' OR '; + } + + return queryString; + }, ''); +}; + +const batchActionToOperation = ( + action: Action, + resetType?: 'first' | 'last', +): StartBatchOperationRequest => { + const identity = getAuthUser().email; + + switch (action) { + case Action.Cancel: + return { + cancellationOperation: { identity }, + }; + case Action.Terminate: + return { + terminationOperation: { identity }, + }; + case Action.Reset: { + const options = + resetType === 'first' + ? { firstWorkflowTask: {} } + : { lastWorkflowTask: {} }; + + return { + resetOperation: { + identity, + // options is a new field for server versions 1.23 and later + options, + // resetType is a deprecated field for server versions 1.23 and earlier + resetType: + resetType === 'first' + ? ResetType.RESET_TYPE_FIRST_WORKFLOW_TASK + : ResetType.RESET_TYPE_LAST_WORKFLOW_TASK, + }, + }; + } + } +}; + +const toWorkflowExecutionInput = ({ + id, + runId, +}: WorkflowExecution): WorkflowExecutionInput => ({ workflowId: id, runId }); + +const createBatchOperationRequest = ( + action: Action, + options: CreateBatchOperationOptions, +): StartBatchOperationRequest => { + const body: StartBatchOperationRequest = { + jobId: options.jobId, + namespace: options.namespace, + reason: options.reason, + ...batchActionToOperation(action, options.resetType), + }; + + if (options.workflows) { + if (isVersionNewer(get(temporalVersion), '1.19')) { + return { + ...body, + executions: options.workflows.map(toWorkflowExecutionInput), + }; + } else { + return { + ...body, + visibilityQuery: queryFromWorkflows(options.workflows), + }; + } + } else if (options.query) { + return { + ...body, + visibilityQuery: options.query, + }; + } +}; + +export async function batchCancelWorkflows( + options: CreateBatchOperationOptions, +): Promise { + const route = routeForApi('batch-operations', { + namespace: options.namespace, + batchJobId: options.jobId, + }); + + const body: StartBatchOperationRequest = createBatchOperationRequest( + Action.Cancel, + options, + ); + + await requestFromAPI(route, { + options: { + method: 'POST', + body: stringifyWithBigInt(body), + }, + notifyOnError: false, + }); + + inProgressBatchOperation.set({ + jobId: body.jobId, + namespace: body.namespace, + }); +} + +export async function batchTerminateWorkflows( + options: CreateBatchOperationOptions, +): Promise { + const route = routeForApi('batch-operations', { + namespace: options.namespace, + batchJobId: options.jobId, + }); + + const body = createBatchOperationRequest(Action.Terminate, options); + + await requestFromAPI(route, { + options: { + method: 'POST', + body: stringifyWithBigInt(body), + }, + notifyOnError: false, + }); + + inProgressBatchOperation.set({ + jobId: body.jobId, + namespace: body.namespace, + }); +} + +export const batchResetWorkflows = async ( + options: CreateBatchOperationOptions, +): Promise => { + const route = routeForApi('batch-operations', { + namespace: options.namespace, + batchJobId: options.jobId, + }); + + const body = createBatchOperationRequest(Action.Reset, options); + + await requestFromAPI(route, { + options: { + method: 'POST', + body: stringifyWithBigInt(body), + }, + notifyOnError: false, + }); + + inProgressBatchOperation.set({ + jobId: body.jobId, + namespace: body.namespace, + }); +}; + +export async function pollBatchOperation({ + namespace, + jobId, +}: DescribeBatchOperationOptions): Promise { + return new Promise((resolve, reject) => { + describeBatchOperation({ namespace, jobId }).then( + ({ state, completeOperationCount }) => { + if (state === 'Failed') { + reject(); + } else if (state !== 'Running') { + resolve(completeOperationCount); + } else { + setTimeout(() => { + try { + resolve(pollBatchOperation({ namespace, jobId })); + } catch { + reject(); + } + }, 5000); + } + }, + ); + }); +} + +export async function describeBatchOperation( + { jobId, namespace }: DescribeBatchOperationOptions, + request = fetch, +): Promise { + const route = routeForApi('batch-operations', { + namespace, + batchJobId: jobId, + }); + + const response = await requestFromAPI(route, { + request, + }); + + return toBatchOperationDetails(response); +} + +const toBatchOperationDetails = ( + apiBatchOperationDetails: DescribeBatchOperationResponse, +): BatchOperation => { + return { + ...apiBatchOperationDetails, + operationType: toBatchOperationTypeReadable( + apiBatchOperationDetails.operationType, + ), + state: toBatchOperationStateReadable(apiBatchOperationDetails.state), + startTime: apiBatchOperationDetails.startTime, + closeTime: apiBatchOperationDetails.closeTime, + totalOperationCount: parseInt( + apiBatchOperationDetails?.totalOperationCount ?? '0', + 10, + ), + completeOperationCount: parseInt( + apiBatchOperationDetails?.completeOperationCount ?? '0', + 10, + ), + failureOperationCount: parseInt( + apiBatchOperationDetails?.failureOperationCount ?? '0', + 10, + ), + }; +}; + +export async function listBatchOperations( + namespace: string, + request = fetch, +): Promise { + const route = routeForApi('batch-operations.list', { + namespace, + batchJobId: '', + }); + + const response = await requestFromAPI(route, { + request, + }); + + return { + nextPageToken: response.nextPageToken, + operations: response.operationInfo + ? response.operationInfo.map(toBatchOperationInfo) + : [], + }; +} + +const toBatchOperationInfo = ( + apiBatchOperationInfo: APIBatchOperationInfo, +): BatchOperationInfo => { + return { + startTime: apiBatchOperationInfo.startTime, + closeTime: apiBatchOperationInfo.closeTime, + jobId: apiBatchOperationInfo.jobId, + state: toBatchOperationStateReadable(apiBatchOperationInfo.state), + }; +}; diff --git a/src/lib/services/cluster-service.ts b/src/lib/services/cluster-service.ts index 533ed5f96..dfa447326 100644 --- a/src/lib/services/cluster-service.ts +++ b/src/lib/services/cluster-service.ts @@ -1,7 +1,7 @@ -import { cluster } from '$lib/stores/cluster'; +import type { GetClusterInfoResponse, GetSystemInfoResponse } from '$lib/types'; +import type { Settings } from '$lib/types/global'; import { requestFromAPI } from '$lib/utilities/request-from-api'; import { routeForApi } from '$lib/utilities/route-for-api'; -import type { GetClusterInfoResponse } from '$types'; export const fetchCluster = async ( settings: Settings, @@ -9,11 +9,24 @@ export const fetchCluster = async ( ): Promise => { if (settings.runtimeEnvironment.isCloud) return; - const route = await routeForApi('cluster'); + const route = routeForApi('cluster'); return await requestFromAPI(route, { request, }).then((clusterInformation) => { - cluster.set(clusterInformation); return clusterInformation; }); }; + +export const fetchSystemInfo = async ( + settings: Settings, + request = fetch, +): Promise => { + if (settings.runtimeEnvironment.isCloud) return; + + const route = routeForApi('systemInfo'); + return await requestFromAPI(route, { + request, + }).then((systemInformation) => { + return systemInformation; + }); +}; diff --git a/src/lib/services/data-converter.ts b/src/lib/services/data-converter.ts deleted file mode 100644 index 3687b5481..000000000 --- a/src/lib/services/data-converter.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - setLastDataConverterFailure, - setLastDataConverterSuccess, -} from '$lib/stores/data-converter-config'; -import { - parseWithBigInt, - stringifyWithBigInt, -} from '$lib/utilities/parse-with-big-int'; -import type { Payload } from '$types'; -import type WebSocketAsPromised from 'websocket-as-promised'; - -interface WebSocketResponse { - content: string; - requestId: string; -} - -export async function convertPayloadWithWebsocket( - payload: Payload, - websocket: WebSocketAsPromised, -): Promise { - if (!websocket.isOpened) { - try { - await websocket.open(); - } catch (_e) { - setLastDataConverterFailure(`Error opening websocket: ${_e}`); - } - } - - if (!websocket.isOpened) { - return Promise.resolve(payload); - } - - const socketResponse: Promise = websocket - .sendRequest({ - payload: stringifyWithBigInt(payload), - }) - .then((response: WebSocketResponse) => { - setLastDataConverterSuccess(); - - try { - const decodedResponse = parseWithBigInt(response.content); - return decodedResponse; - } catch { - // This doesn't seem to be a failure the worker _could_ send back a text response - // instead of JSON - return response.content; - } - }) - .catch((error) => { - setLastDataConverterFailure( - `Error decoding websocket response: ${error}`, - ); - }); - - return socketResponse; -} diff --git a/src/lib/services/data-encoder.test.ts b/src/lib/services/data-encoder.test.ts new file mode 100644 index 000000000..46b1bdbe4 --- /dev/null +++ b/src/lib/services/data-encoder.test.ts @@ -0,0 +1,93 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { codeServerRequest } from './data-encoder'; + +const settings = { + codec: { + endpoint: 'http://localcodecserver.com', + passAccessToken: false, + includeCredentials: false, + }, +}; + +describe('Codec Server Requests for Decode and Encode', () => { + const payloads = { payloads: [{}] }; + const namespace = 'test-namespace'; + + it('should send a request and return decoded payloads', async () => { + global.fetch = vi.fn(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve(payloads), + } as Response), + ); + + const namespace = 'test-namespace'; + const response = await codeServerRequest({ + type: 'decode', + payloads, + namespace, + settings, + }); + expect(response).toEqual(payloads); + }); + + it('should return original payloads for decode on failure', async () => { + global.fetch = vi.fn(() => + Promise.resolve({ + ok: false, + status: 500, + statusText: 'Internal Server Error', + } as Response), + ); + const response = await codeServerRequest({ + type: 'decode', + payloads, + namespace, + settings, + }); + expect(response).toEqual(payloads); + }); + + it('should send a request and return encoded payloads', async () => { + global.fetch = vi.fn(() => + Promise.resolve({ + ok: true, + json: () => Promise.resolve(payloads), + } as Response), + ); + + const response = await codeServerRequest({ + type: 'encode', + payloads, + namespace, + settings, + }); + expect(response).toEqual(payloads); + }); + + it('should throw an error for encode on failure', async () => { + global.fetch = vi.fn(() => + Promise.resolve({ + ok: false, + status: 500, + statusText: 'Internal Server Error', + } as Response), + ); + await expect( + codeServerRequest({ type: 'encode', payloads, namespace, settings }), + ).rejects.toThrow(); + }); + + it('should throw an error for encode if response is not ok', async () => { + const response = new Response(JSON.stringify(payloads), { + status: 500, + statusText: 'Internal Server Error', + }); + global.fetch = vi.fn(() => Promise.resolve(response)); + + await expect( + codeServerRequest({ type: 'encode', payloads, namespace, settings }), + ).rejects.toThrow(); + }); +}); diff --git a/src/lib/services/data-encoder.ts b/src/lib/services/data-encoder.ts index db91481a4..49d368b0b 100644 --- a/src/lib/services/data-encoder.ts +++ b/src/lib/services/data-encoder.ts @@ -1,25 +1,38 @@ +import { get } from 'svelte/store'; + +import { page } from '$app/stores'; + +import { translate } from '$lib/i18n/translate'; +import { authUser } from '$lib/stores/auth-user'; import { setLastDataEncoderFailure, setLastDataEncoderSuccess, } from '$lib/stores/data-encoder-config'; +import type { NetworkError, Settings } from '$lib/types/global'; +import { + getCodecEndpoint, + getCodecIncludeCredentials, + getCodecPassAccessToken, +} from '$lib/utilities/get-codec'; import { validateHttps } from '$lib/utilities/is-http'; import { stringifyWithBigInt } from '$lib/utilities/parse-with-big-int'; -import type { Payloads } from '$types'; +export type PotentialPayloads = { payloads: unknown[] }; -export async function convertPayloadsWithCodec({ +export async function codeServerRequest({ + type, payloads, - namespace, - settings, - accessToken, + namespace = get(page).params.namespace, + settings = get(page).data.settings, }: { - payloads: Payloads; - namespace: string; - settings: Settings; - accessToken: string; -}): Promise { - const endpoint = settings?.codec?.endpoint; - const passAccessToken = settings?.codec?.passAccessToken; + type: 'decode' | 'encode'; + payloads: PotentialPayloads; + namespace?: string; + settings?: Settings; +}): Promise { + const endpoint = getCodecEndpoint(settings); + const passAccessToken = getCodecPassAccessToken(settings); + const includeCredentials = getCodecIncludeCredentials(settings); const headers = { 'Content-Type': 'application/json', @@ -28,30 +41,86 @@ export async function convertPayloadsWithCodec({ if (passAccessToken) { if (validateHttps(endpoint)) { + let accessToken = get(authUser).accessToken; + const accessTokenExtras = get(authUser).idToken; + if (globalThis?.AccessToken) { + accessToken = await globalThis?.AccessToken(); + } else if (accessTokenExtras) { + headers['Authorization-Extras'] = accessTokenExtras; + } headers['Authorization'] = `Bearer ${accessToken}`; } else { setLastDataEncoderFailure(); - return payloads; } } - const encoderResponse: Promise = fetch(endpoint + '/decode', { - headers, - method: 'POST', - body: stringifyWithBigInt(payloads), - }) - .then((r) => r.json()) + const requestOptions = includeCredentials + ? { + headers, + credentials: 'include' as RequestCredentials, + method: 'POST', + body: stringifyWithBigInt(payloads), + } + : { + headers, + method: 'POST', + body: stringifyWithBigInt(payloads), + }; + + const decoderResponse: Promise = fetch( + endpoint + `/${type}`, + requestOptions, + ) + .then((response) => { + if (response.ok === false) { + throw { + statusCode: response.status, + statusText: response.statusText, + response, + message: translate(`common.${type}-failed`), + } as NetworkError; + } else { + return response.json(); + } + }) .then((response) => { setLastDataEncoderSuccess(); return response; }) - .catch(() => { - setLastDataEncoderFailure(); - - return payloads; + .catch((err: unknown) => { + setLastDataEncoderFailure(err); + if (type === 'decode') { + return payloads; + } else { + throw err; + } }); - return encoderResponse; + return decoderResponse; +} + +export async function decodePayloadsWithCodec({ + payloads, + namespace = get(page).params.namespace, + settings = get(page).data.settings, +}: { + payloads: PotentialPayloads; + namespace?: string; + settings?: Settings; +}): Promise { + return codeServerRequest({ type: 'decode', payloads, namespace, settings }); +} + +export async function encodePayloadsWithCodec({ + payloads, + namespace = get(page).params.namespace, + settings = get(page).data.settings, +}: { + payloads: PotentialPayloads; + namespace?: string; + settings?: Settings; +}): Promise { + return codeServerRequest({ type: 'encode', payloads, namespace, settings }); } diff --git a/src/lib/services/deployments-service.ts b/src/lib/services/deployments-service.ts new file mode 100644 index 000000000..240de1aad --- /dev/null +++ b/src/lib/services/deployments-service.ts @@ -0,0 +1,57 @@ +import type { + DeploymentParameters, + DeploymentVersionParameters, + ListWorkerDeploymentsResponse, + WorkerDeploymentResponse, + WorkerDeploymentSummary, + WorkerDeploymentVersionResponse, +} from '$lib/types/deployments'; +import type { ErrorCallback } from '$lib/utilities/request-from-api'; +import { requestFromAPI } from '$lib/utilities/request-from-api'; +import { routeForApi } from '$lib/utilities/route-for-api'; + +type PaginatedDeploymentsPromise = ( + pageSize: number, + token: string, +) => Promise<{ items: WorkerDeploymentSummary[]; nextPageToken: string }>; + +export const fetchPaginatedDeployments = async ( + namespace: string, + query: string, + onError: ErrorCallback, + request = fetch, +): Promise => { + return (pageSize = 100, token = '') => { + const route = routeForApi('worker-deployments', { namespace }); + return requestFromAPI(route, { + params: { + maximumPageSize: String(pageSize), + nextPageToken: token, + ...(query ? { query } : {}), + }, + request, + onError, + }).then(({ workerDeployments, nextPageToken }) => { + return { + items: workerDeployments, + nextPageToken: nextPageToken ? String(nextPageToken) : '', + }; + }); + }; +}; + +export const fetchDeployment = async ( + parameters: DeploymentParameters, + request = fetch, +): Promise => { + const route = routeForApi('worker-deployment', parameters); + return requestFromAPI(route, { request }); +}; + +export const fetchDeploymentVersion = async ( + parameters: DeploymentVersionParameters, + request = fetch, +): Promise => { + const route = routeForApi('worker-deployment-version', parameters); + return requestFromAPI(route, { request }); +}; diff --git a/src/lib/services/events-service.ts b/src/lib/services/events-service.ts index 967dc3282..b230e7dfb 100644 --- a/src/lib/services/events-service.ts +++ b/src/lib/services/events-service.ts @@ -1,11 +1,27 @@ -import type { GetWorkflowExecutionHistoryResponse } from '$types'; +import throttle from 'just-throttle'; -import { paginated } from '$lib/utilities/paginated'; -import { requestFromAPI } from '$lib/utilities/request-from-api'; -import { routeForApi } from '$lib/utilities/route-for-api'; import { toEventHistory } from '$lib/models/event-history'; import type { EventSortOrder } from '$lib/stores/event-view'; +import { fullEventHistory } from '$lib/stores/events'; +import { refresh } from '$lib/stores/workflow-run'; +import type { WorkflowAPIRoutePath } from '$lib/types/api'; +import type { + CommonHistoryEvent, + GetWorkflowExecutionHistoryResponse, + HistoryEvent, + WorkflowEvent, + WorkflowEvents, +} from '$lib/types/events'; +import type { + NamespaceScopedRequest, + NextPageToken, + PaginationCallbacks, + Settings, +} from '$lib/types/global'; import { isSortOrder } from '$lib/utilities/is'; +import { paginated } from '$lib/utilities/paginated'; +import { requestFromAPI } from '$lib/utilities/request-from-api'; +import { routeForApi } from '$lib/utilities/route-for-api'; export type FetchEventsParameters = NamespaceScopedRequest & PaginationCallbacks & { @@ -13,6 +29,9 @@ export type FetchEventsParameters = NamespaceScopedRequest & runId: string; rawPayloads?: boolean; sort?: EventSortOrder; + signal?: AbortSignal; + historySize?: string; + maximumPageSize?: string; }; export type FetchEventsParametersWithSettings = FetchEventsParameters & { @@ -20,6 +39,22 @@ export type FetchEventsParametersWithSettings = FetchEventsParameters & { accessToken: string; }; +export const getEndpointForRawHistory = ({ + namespace, + workflowId, + runId, +}: FetchEventsParameters): string => { + return routeForApi( + 'events.raw', + { + namespace, + workflowId, + runId, + }, + true, + ); +}; + const getEndpointForSortOrder = ( sortOrder: EventSortOrder, ): WorkflowAPIRoutePath => { @@ -39,12 +74,13 @@ export const fetchRawEvents = async ({ onComplete, }: FetchEventsParameters): Promise => { const endpoint = getEndpointForSortOrder(sort); - const route = await routeForApi(endpoint, { namespace, workflowId, runId }); + const route = routeForApi(endpoint, { namespace, workflowId }); const response = await paginated( async (token: string) => { return requestFromAPI(route, { token, request: fetch, + params: { 'execution.runId': runId }, }); }, { onStart, onUpdate, onComplete }, @@ -53,11 +89,148 @@ export const fetchRawEvents = async ({ return response.history.events; }; -export const fetchEvents = async ( - parameters: FetchEventsParametersWithSettings, -): Promise => { - const { settings, namespace, accessToken } = parameters; - return fetchRawEvents(parameters).then((response) => - toEventHistory({ response, namespace, settings, accessToken }), +export const throttleRefresh = throttle(() => { + refresh.set(Date.now()); +}, 5000); + +export const fetchAllEvents = async ({ + namespace, + workflowId, + runId, + sort = 'ascending', + signal, + historySize, +}: FetchEventsParameters): Promise => { + const onStart = () => { + if (!signal) return; + fullEventHistory.set([]); + }; + + const onUpdate = (full, current) => { + if (!signal) return; + fullEventHistory.set([...toEventHistory(full.history?.events)]); + const next = current?.history?.events; + const hasNewHistory = + historySize && + next?.every((e) => parseInt(e.eventId) > parseInt(historySize)); + if (hasNewHistory) { + throttleRefresh(); + } + }; + + const onComplete = () => { + if (!signal) return; + refresh.set(Date.now()); + }; + + const endpoint = getEndpointForSortOrder(sort); + const route = routeForApi(endpoint, { namespace, workflowId }); + const response = await paginated( + async (token: string) => { + return requestFromAPI(route, { + token, + request: fetch, + params: { + 'execution.runId': runId, + waitNewEvent: signal ? 'true' : 'false', + }, + options: { signal }, + }); + }, + { onStart, onUpdate, onComplete }, ); + + if (!response?.history) return []; + const allEvents = await toEventHistory(response.history.events); + return allEvents; +}; + +export const fetchPartialRawEvents = async ({ + namespace, + workflowId, + runId, + sort, + maximumPageSize = '20', +}: FetchEventsParameters): Promise => { + const route = routeForApi(`events.${sort}`, { + namespace, + workflowId, + }); + + try { + const response = await requestFromAPI( + route, + { + request: fetch, + params: { maximumPageSize, 'execution.runId': runId }, + }, + ); + + return response.history.events; + } catch (e) { + return []; + } +}; + +type PaginatedEventParams = { + namespace: string; + workflowId: string; + runId: string; + sort?: EventSortOrder; + category?: string; + compact: boolean; +}; + +export async function getPaginatedEvents({ + namespace, + workflowId, + runId, + sort, + category, + compact, +}: PaginatedEventParams): Promise< + () => Promise<{ items: WorkflowEvents; nextPageToken: NextPageToken }> +> { + return async (_pageSize = 100, token = '') => { + const historyRoute = routeForApi( + compact ? 'events.ascending' : `events.${sort}`, + { + namespace, + workflowId, + }, + ); + const { history, nextPageToken } = + await requestFromAPI(historyRoute, { + request: fetch, + params: { + nextPageToken: token, + 'execution.runId': runId, + }, + }); + + const events = await toEventHistory(history.events); + + if (category) { + return { + items: events?.filter((event) => event.category === category), + nextPageToken: nextPageToken ?? '', + }; + } + return { + items: events, + nextPageToken: nextPageToken ?? '', + }; + }; +} + +export const fetchInitialEvent = async ( + parameters: FetchEventsParameters, +): Promise => { + const startEventsRaw = await fetchPartialRawEvents({ + ...parameters, + sort: 'ascending', + maximumPageSize: '1', + }); + const start = await toEventHistory(startEventsRaw); + return start[0]; }; diff --git a/src/lib/services/github-service.ts b/src/lib/services/github-service.ts deleted file mode 100644 index d759c5689..000000000 --- a/src/lib/services/github-service.ts +++ /dev/null @@ -1,24 +0,0 @@ -export const fetchLatestUiVersion = async ( - request = fetch, -): Promise => { - try { - const response = await request( - 'https://api.github.com/repos/temporalio/ui-server/releases', - ); - const body = await response.json(); - - if (!response.ok) { - return; - } - - let version = undefined; - if (body.length > 0) { - const { tag_name } = body[0]; - version = tag_name.replace('v', ''); - } - - return version; - } catch (e) { - return ''; - } -}; diff --git a/src/lib/services/namespaces-service.test.ts b/src/lib/services/namespaces-service.test.ts new file mode 100644 index 000000000..981887182 --- /dev/null +++ b/src/lib/services/namespaces-service.test.ts @@ -0,0 +1,117 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { fetchNamespaces } from './namespaces-service'; +import { namespaces } from '../stores/namespaces'; +import { toaster } from '../stores/toaster'; + +vi.mock('../stores/toaster', () => ({ toaster: { push: vi.fn() } })); +vi.mock('../stores/namespaces', () => ({ namespaces: { set: vi.fn() } })); + +const createSuccessfulRequest = () => + vi.fn(() => + Promise.resolve({ + ok: true, + status: 200, + json: () => + Promise.resolve({ + namespaces: [ + { namespaceInfo: { name: 'temporal-system' } }, + { namespaceInfo: { name: 'default' } }, + ], + }), + }), + ) as unknown as typeof fetch; + +const createUnsuccessfulRequest = () => + vi.fn(() => + Promise.resolve({ + ok: false, + status: 500, + statusText: 'error', + json: () => Promise.resolve({}), + }), + ) as unknown as typeof fetch; + +describe('fetchNamespaces', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should call fetch with the correct route', async () => { + const request = createSuccessfulRequest(); + + await fetchNamespaces({ runtimeEnvironment: { isCloud: false } }, request); + expect(request).toHaveBeenCalledWith( + 'http://localhost:8233/api/v1/namespaces?', + { + credentials: 'include', + headers: { + 'Caller-Type': 'operator', + }, + }, + ); + }); + + it('should return an empty array if the runtime environment is cloud', async () => { + const request = createSuccessfulRequest(); + + await fetchNamespaces({ runtimeEnvironment: { isCloud: true } }, request); + + expect(request).not.toHaveBeenCalled(); + expect(namespaces.set).toHaveBeenCalledWith([]); + }); + + it('should return an empty array if the request fails', async () => { + const request = createUnsuccessfulRequest(); + + await fetchNamespaces({ runtimeEnvironment: { isCloud: false } }, request); + + expect(request).toHaveBeenCalled(); + expect(namespaces.set).toHaveBeenCalledWith([]); + }); + + it('should display a toast message if the request fails', async () => { + const request = createUnsuccessfulRequest(); + + await fetchNamespaces({ runtimeEnvironment: { isCloud: false } }, request); + + expect(request).toHaveBeenCalled(); + expect(toaster.push).toHaveBeenCalledWith({ + message: 'Unable to fetch namespaces', + variant: 'error', + }); + }); + + it('should not include "temporal-system" if showTemporalSystemNamespace is false', async () => { + const request = createSuccessfulRequest(); + + await fetchNamespaces( + { + runtimeEnvironment: { isCloud: false }, + showTemporalSystemNamespace: false, + }, + request, + ); + + expect(namespaces.set).toHaveBeenCalledWith([ + { namespaceInfo: { name: 'default' } }, + ]); + }); + + it('should include "temporal-system" if showTemporalSystemNamespace is true', async () => { + const request = createSuccessfulRequest(); + + await fetchNamespaces( + { + runtimeEnvironment: { isCloud: false }, + showTemporalSystemNamespace: true, + }, + request, + ); + + expect(namespaces.set).toHaveBeenCalledWith([ + { namespaceInfo: { name: 'temporal-system' } }, + { namespaceInfo: { name: 'default' } }, + ]); + }); +}); diff --git a/src/lib/services/namespaces-service.ts b/src/lib/services/namespaces-service.ts index a9ed03e30..a16e48995 100644 --- a/src/lib/services/namespaces-service.ts +++ b/src/lib/services/namespaces-service.ts @@ -1,42 +1,75 @@ -import { notifications } from '$lib/stores/notifications'; import { namespaces } from '$lib/stores/namespaces'; +import { toaster } from '$lib/stores/toaster'; +import type { + DescribeNamespaceResponse, + ListNamespacesResponse, +} from '$lib/types'; +import type { Settings } from '$lib/types/global'; import { paginated } from '$lib/utilities/paginated'; import { requestFromAPI } from '$lib/utilities/request-from-api'; import { routeForApi } from '$lib/utilities/route-for-api'; - -import type { DescribeNamespaceResponse, ListNamespacesResponse } from '$types'; +import { + toNamespaceArchivalStateReadable, + toNamespaceStateReadable, +} from '$lib/utilities/screaming-enums'; const emptyNamespace = { namespaces: [], }; +const toNamespaceDetails = ( + namespace: DescribeNamespaceResponse, +): DescribeNamespaceResponse => { + if (namespace.config) { + namespace.config.historyArchivalState = toNamespaceArchivalStateReadable( + namespace.config.historyArchivalState, + ); + namespace.config.visibilityArchivalState = toNamespaceArchivalStateReadable( + namespace.config.visibilityArchivalState, + ); + } + + if (namespace.namespaceInfo) { + namespace.namespaceInfo.state = toNamespaceStateReadable( + namespace.namespaceInfo?.state, + ); + } + return namespace; +}; + export async function fetchNamespaces( settings: Settings, request = fetch, ): Promise { - if (settings.runtimeEnvironment.isCloud) { + const { showTemporalSystemNamespace, runtimeEnvironment } = settings; + + if (runtimeEnvironment.isCloud) { namespaces.set([]); return; } try { - const route = await routeForApi('namespaces'); + const route = routeForApi('namespaces'); const results = await paginated(async (token: string) => requestFromAPI(route, { request, token, - onError: () => notifications.add('error', 'Unable to fetch namespaces'), + onError: () => + toaster.push({ + variant: 'error', + message: 'Unable to fetch namespaces', + }), }), ); - const { showTemporalSystemNamespace } = settings; - const _namespaces: DescribeNamespaceResponse[] = ( - results?.namespaces ?? [] - ).filter( - (namespace: DescribeNamespaceResponse) => - showTemporalSystemNamespace || - namespace.namespaceInfo.name !== 'temporal-system', - ); + const _namespaces: DescribeNamespaceResponse[] = (results?.namespaces ?? []) + .filter( + (namespace: DescribeNamespaceResponse) => + showTemporalSystemNamespace || + namespace.namespaceInfo.name !== 'temporal-system', + ) + .map(toNamespaceDetails); + namespaces.set(_namespaces); } catch (e) { namespaces.set([]); @@ -54,11 +87,12 @@ export async function fetchNamespace( return empty; } - const route = await routeForApi('namespace', { namespace }); + const route = routeForApi('namespace', { namespace }); const results = await requestFromAPI(route, { request, - onError: () => notifications.add('error', 'Unable to fetch namespaces'), + onError: () => + toaster.push({ variant: 'error', message: 'Unable to fetch namespaces' }), }); - return results ?? empty; + return results ? toNamespaceDetails(results) : empty; } diff --git a/src/lib/services/nexus-service.ts b/src/lib/services/nexus-service.ts new file mode 100644 index 000000000..52289d96e --- /dev/null +++ b/src/lib/services/nexus-service.ts @@ -0,0 +1,83 @@ +import type { NexusEndpoint } from '$lib/types/nexus'; +import { stringifyWithBigInt } from '$lib/utilities/parse-with-big-int'; +import { requestFromAPI } from '$lib/utilities/request-from-api'; +import { routeForApi } from '$lib/utilities/route-for-api'; + +type NexusEndpoints = { endpoints: NexusEndpoint[] }; + +export const fetchNexusEndpoints = async ( + search: string = '', + request = fetch, +): Promise => { + const route = routeForApi('nexus-endpoints'); + const { endpoints } = await requestFromAPI(route, { + request, + params: { name: search }, + }); + return endpoints || []; +}; + +export const fetchNexusEndpoint = async ( + id: string, + request = fetch, +): Promise<{ endpoint: NexusEndpoint }> => { + const route = routeForApi('nexus-endpoint', { endpointId: id }); + const endpoint = await requestFromAPI<{ endpoint: NexusEndpoint }>(route, { + request, + notifyOnError: false, + }); + return endpoint; +}; + +export const createNexusEndpoint = async ( + body: Partial, + request = fetch, +): Promise => { + const route = routeForApi('nexus-endpoints'); + const endpoint: NexusEndpoint = await requestFromAPI(route, { + request, + options: { + method: 'POST', + body: stringifyWithBigInt({ + ...body, + }), + }, + notifyOnError: false, + }); + return endpoint; +}; + +export const updateNexusEndpoint = async ( + id: string, + body: Partial, + request = fetch, +): Promise => { + const route = routeForApi('nexus-endpoint.update', { endpointId: id }); + const endpoint: NexusEndpoint = await requestFromAPI(route, { + request, + options: { + method: 'POST', + body: stringifyWithBigInt({ + ...body, + }), + }, + notifyOnError: false, + }); + return endpoint; +}; + +export const deleteNexusEndpoint = async ( + id: string, + version: string, + request = fetch, +): Promise => { + const route = routeForApi('nexus-endpoint', { endpointId: id }); + const endpoint: NexusEndpoint = await requestFromAPI(route, { + request, + options: { + method: 'DELETE', + }, + params: { version }, + }); + return endpoint; +}; diff --git a/src/lib/services/pollers-service.ts b/src/lib/services/pollers-service.ts index 187f053d5..b43879f6c 100644 --- a/src/lib/services/pollers-service.ts +++ b/src/lib/services/pollers-service.ts @@ -1,12 +1,62 @@ -import { requestFromAPI } from '$lib/utilities/request-from-api'; +import { translate } from '$lib/i18n/translate'; +import type { + BuildIdReachability, + PollerInfo, + TaskQueueCompatibleVersionSet, + TaskQueueStatus, +} from '$lib/types'; +import type { RoutingConfig } from '$lib/types/deployments'; +import type { NamespaceScopedRequest } from '$lib/types/global'; +import { + type APIErrorResponse, + requestFromAPI, +} from '$lib/utilities/request-from-api'; import { routeForApi } from '$lib/utilities/route-for-api'; -import type { PollerInfo, TaskQueueStatus } from '$types'; +import { getOrderedVersionSets } from '$lib/utilities/task-queue-compatibility'; export type GetAllPollersRequest = NamespaceScopedRequest & { queue: string }; +export type GetWorkerTaskReachabilityRequest = NamespaceScopedRequest & { + queue: string; + buildIds: string[]; +}; + export type GetPollersResponse = { - pollers: PollerWithTaskQueueTypes[]; + pollers?: PollerWithTaskQueueTypes[]; taskQueueStatus: TaskQueueStatus; + versioningInfo?: RoutingConfig; +}; + +export type AssignmentRule = { + rule: { + targetBuildId: string; + percentageRamp?: { + rampPercentage?: number; + }; + }; + createTime: string; +}; + +type CompatibleRedirectRule = { + rule: { + sourceBuildId: string; + targetBuildId: string; + }; + createTime: string; +}; + +export type TaskQueueRules = { + assignmentRules?: AssignmentRule[]; + compatibleRedirectRules?: CompatibleRedirectRule[]; + conflictToken: string; +}; + +export type TaskQueueCompatibility = { + majorVersionSets: TaskQueueCompatibleVersionSet[]; +}; + +export type WorkerReachability = { + buildIdReachability: BuildIdReachability[]; }; type PollersData = { @@ -28,7 +78,7 @@ export async function getPollers( parameters: GetAllPollersRequest, request = fetch, ): Promise { - const route = await routeForApi('task-queue', parameters); + const route = routeForApi('task-queue', parameters); const workflowPollers = await requestFromAPI(route, { request, params: { taskQueueType: '1' }, @@ -39,6 +89,9 @@ export async function getPollers( params: { taskQueueType: '2' }, }); + if (!workflowPollers?.pollers) workflowPollers.pollers = []; + if (!activityPollers?.pollers) activityPollers.pollers = []; + activityPollers.pollers.forEach((poller: PollerWithTaskQueueTypes) => { poller.taskQueueTypes = ['ACTIVITY']; }); @@ -78,7 +131,7 @@ export async function getPollers( }), ); - activityPollers.pollers.reduce( + activityPollers.pollers?.reduce( r('ACTIVITY'), workflowPollers.pollers.reduce(r('WORKFLOW'), {}), ); @@ -89,8 +142,110 @@ export async function getPollers( const taskQueueStatus = !activityPollers.pollers.length ? workflowPollers.taskQueueStatus : activityPollers.taskQueueStatus; + const versioningInfo = !activityPollers.pollers.length + ? workflowPollers.versioningInfo + : activityPollers.versioningInfo; + return { pollers, taskQueueStatus, + versioningInfo, }; } +export type VersionResults = { + rules: TaskQueueRules; + compatibility: TaskQueueCompatibility; + versionSets: TaskQueueCompatibleVersionSet[]; +}; + +export async function getVersioning( + parameters: GetAllPollersRequest, + request = fetch, +): Promise { + const rules = await getTaskQueueRules(parameters, request); + const compatibility = await getTaskQueueCompatibility(parameters, request); + const versionSets = getOrderedVersionSets(compatibility); + + return { rules, compatibility, versionSets }; +} + +export async function getTaskQueueRules( + parameters: GetAllPollersRequest, + request = fetch, +): Promise { + const route = routeForApi('task-queue.rules', parameters); + return requestFromAPI(route, { + request, + handleError: (_e: APIErrorResponse) => { + return; + }, + }); +} + +export async function getTaskQueueCompatibility( + parameters: GetAllPollersRequest, + request = fetch, +): Promise { + const route = routeForApi('task-queue.compatibility', parameters); + return requestFromAPI(route, { + request, + handleError: (_e: APIErrorResponse) => { + return; + }, + }); +} + +export async function getWorkerTaskReachability( + parameters: GetWorkerTaskReachabilityRequest, + request = fetch, +): Promise { + const { namespace, buildIds, queue } = parameters; + const route = routeForApi('worker-task-reachability', { namespace }); + const params = new URLSearchParams(); + + if (buildIds.length) { + for (const buildId of buildIds) { + params.append('buildIds', buildId); + } + } else { + params.append('buildIds', ''); + params.append('taskQueues', queue); + } + + return await requestFromAPI(route, { + request, + params, + handleError: (_e: APIErrorResponse) => { + return { + buildIdReachability: [], + }; + }, + }); +} + +function getLabelForReachability(reachability: unknown[]): string { + if (!reachability || !reachability.length) + return translate('workers.ready-to-be-retired'); + if (reachability.length === 1 && reachability.includes('CLOSED')) { + return translate('common.maybe'); + } + return translate('common.no'); +} + +export function getBuildIdReachability( + workerTaskReachability: WorkerReachability, + taskQueue: string, + buildId: string, +): string { + const buildIdReachability = workerTaskReachability?.buildIdReachability?.find( + (bird) => bird.buildId === buildId, + ); + if (!buildIdReachability) return ''; + const currentTaskQueueReachability = + buildIdReachability?.taskQueueReachability?.find( + (tqr) => tqr?.taskQueue === taskQueue, + ); + if (!currentTaskQueueReachability) return ''; + const reachability = currentTaskQueueReachability?.reachability; + return getLabelForReachability(reachability); +} diff --git a/src/lib/services/query-service.ts b/src/lib/services/query-service.ts index 8e66ba0a1..186cd9cd5 100644 --- a/src/lib/services/query-service.ts +++ b/src/lib/services/query-service.ts @@ -1,27 +1,26 @@ -import { - isTemporalAPIError, - requestFromAPI, -} from '$lib/utilities/request-from-api'; -import { routeForApi } from '$lib/utilities/route-for-api'; - +import { translate } from '$lib/i18n/translate'; +import type { Payloads } from '$lib/types'; +import type { WorkflowQueryRouteParameters } from '$lib/types/api'; +import type { Eventual, Settings } from '$lib/types/global'; +import type { WorkflowMetadata } from '$lib/types/workflows'; +import { convertPayloadToJsonWithCodec } from '$lib/utilities/decode-payload'; import { getQueryTypesFromError } from '$lib/utilities/get-query-types-from-error'; -import { getCodecEndpoint } from '$lib/utilities/get-codec'; -import { - convertPayloadToJsonWithCodec, - convertPayloadToJsonWithWebsocket, -} from '$lib/utilities/decode-payload'; -import { - parseWithBigInt, - stringifyWithBigInt, -} from '$lib/utilities/parse-with-big-int'; +import { has } from '$lib/utilities/has'; +import { stringifyWithBigInt } from '$lib/utilities/parse-with-big-int'; +import { requestFromAPI } from '$lib/utilities/request-from-api'; +import { routeForApi } from '$lib/utilities/route-for-api'; type QueryRequestParameters = { workflow: Eventual<{ id: string; runId: string }>; namespace: string; queryType: string; + queryArgs?: Payloads; }; -type WorkflowParameters = Omit; +type WorkflowParameters = Omit< + QueryRequestParameters, + 'queryType' | 'queryArgs' +>; type QueryPayload = { data: string; @@ -44,27 +43,23 @@ export type ParsedQuery = ReturnType[0]; const formatParameters = async ( namespace: string, workflow: Eventual<{ id: string; runId: string }>, -): Promise => { + queryType: string, +): Promise => { workflow = await workflow; return { namespace, workflowId: workflow.id, - runId: workflow.runId, + queryType, }; }; async function fetchQuery( - { workflow, namespace, queryType }: QueryRequestParameters, - request = fetch, - onError?: (error: { - status: number; - statusText: string; - body: unknown; - }) => void, + { workflow, namespace, queryType, queryArgs }: QueryRequestParameters, + signal?: AbortSignal, ): Promise { workflow = await workflow; - const parameters = await formatParameters(namespace, workflow); - const route = await routeForApi('query', parameters); + const parameters = await formatParameters(namespace, workflow, queryType); + const route = routeForApi('query', parameters); return await requestFromAPI(route, { options: { @@ -76,67 +71,78 @@ async function fetchQuery( }, query: { queryType, + queryArgs, }, }), + signal, }, - request, - onError, + request: fetch, notifyOnError: false, }); } -export async function getQueryTypes( +export async function getWorkflowMetadata( options: WorkflowParameters, - request = fetch, -): Promise { - return new Promise((resolve, reject) => { - fetchQuery( - { ...options, queryType: '@@temporal-internal__list' }, - request, - (response) => { - if ( - isTemporalAPIError(response.body) && - response.body.message.includes('@@temporal-internal__list') - ) { - resolve(getQueryTypesFromError(response.body.message)); - } else { - reject(response); - } - }, + settings: Settings, + accessToken: string, + signal?: AbortSignal, +): Promise { + try { + const metadata = await getQuery( + { ...options, queryType: '__temporal_workflow_metadata' }, + settings, + accessToken, + signal, ); - }); + if (!metadata.currentDetails) { + metadata.currentDetails = translate('workflows.no-current-details'); + } + return metadata; + } catch (e) { + if (e.message?.includes('__temporal_workflow_metadata')) { + const queryDefinitions = getQueryTypesFromError(e.message); + return { + definition: { + queryDefinitions, + }, + currentDetails: translate('workflows.no-current-details'), + }; + } else { + return { + error: e, + currentDetails: translate('workflows.no-current-details'), + }; + } + } } export async function getQuery( options: QueryRequestParameters, settings: Settings, accessToken: string, - request = fetch, + signal?: AbortSignal, ): Promise { - return fetchQuery(options, request).then(async (execution) => { + return fetchQuery(options, signal).then(async (execution) => { const { queryResult } = execution ?? { queryResult: { payloads: [] } }; let data: ParsedQuery = queryResult.payloads; try { if (data[0]) { - const endpoint = getCodecEndpoint(settings); - const _settings = { - ...settings, - codec: { ...settings?.codec, endpoint }, - }; - const convertedAttributes = endpoint - ? await convertPayloadToJsonWithCodec({ - attributes: queryResult, - namespace: options.namespace, - settings: _settings, - accessToken, - }) - : await convertPayloadToJsonWithWebsocket(queryResult); + const convertedAttributes = await convertPayloadToJsonWithCodec({ + attributes: queryResult, + namespace: options.namespace, + settings, + accessToken, + }); - data = convertedAttributes?.payloads[0]; + if ( + has(convertedAttributes, 'payloads') && + Array.isArray(convertedAttributes.payloads) + ) { + data = convertedAttributes.payloads[0]; + } } - - return parseWithBigInt(data); + return data; } catch (e) { if (typeof data !== 'string') { return stringifyWithBigInt(data); diff --git a/src/lib/services/schedule-service.ts b/src/lib/services/schedule-service.ts index 91bb74c7f..411f9a488 100644 --- a/src/lib/services/schedule-service.ts +++ b/src/lib/services/schedule-service.ts @@ -1,16 +1,17 @@ -import type { ErrorCallback } from '$lib/utilities/request-from-api'; import { v4 as uuidv4 } from 'uuid'; +import { translate } from '$lib/i18n/translate'; import type { - ListScheduleResponse, - DescribeScheduleResponse, CreateScheduleRequest, - UpdateScheduleRequest, + ListScheduleResponse, ScheduleListEntry, -} from '$types'; + UpdateScheduleRequest, +} from '$lib/types'; +import type { DescribeFullSchedule, OverlapPolicy } from '$lib/types/schedule'; +import { stringifyWithBigInt } from '$lib/utilities/parse-with-big-int'; +import type { ErrorCallback } from '$lib/utilities/request-from-api'; import { requestFromAPI } from '$lib/utilities/request-from-api'; import { routeForApi } from '$lib/utilities/route-for-api'; -import { stringifyWithBigInt } from '$lib/utilities/parse-with-big-int'; type ScheduleParameters = { namespace: string; @@ -25,6 +26,36 @@ export type ScheduleResponse = { export type FetchSchedule = typeof fetchAllSchedules; +type PaginatedSchedulesPromise = ( + pageSize: number, + token: string, +) => Promise<{ items: ScheduleListEntry[]; nextPageToken: string }>; + +export const fetchPaginatedSchedules = async ( + namespace: string, + query: string, + onError: ErrorCallback, + request = fetch, +): Promise => { + return (pageSize = 100, token = '') => { + const route = routeForApi('schedules', { namespace }); + return requestFromAPI(route, { + params: { + maximumPageSize: String(pageSize), + nextPageToken: token, + ...(query ? { query } : {}), + }, + request, + onError, + }).then(({ schedules, nextPageToken }) => { + return { + items: schedules, + nextPageToken: nextPageToken ? String(nextPageToken) : '', + }; + }); + }; +}; + export const fetchAllSchedules = async ( namespace: string, request = fetch, @@ -33,9 +64,11 @@ export const fetchAllSchedules = async ( const onError: ErrorCallback = (err) => (error = err?.body?.message ?? - `Error fetching schedules: ${err.status}: ${err.statusText}`); + `${translate('schedules.error-message-fetching')}: ${err.status}: ${ + err.statusText + }`); - const route = await routeForApi('schedules', { namespace }); + const route = routeForApi('schedules', { namespace }); const { schedules, nextPageToken } = (await requestFromAPI(route, { params: {}, @@ -54,7 +87,7 @@ export async function fetchSchedule( parameters: ScheduleParameters, request = fetch, ): Promise { - const route = await routeForApi('schedule', parameters); + const route = routeForApi('schedule', parameters); return requestFromAPI(route, { request }); } @@ -62,7 +95,7 @@ export async function deleteSchedule( parameters: ScheduleParameters, request = fetch, ): Promise { - const route = await routeForApi('schedule.delete', parameters); + const route = routeForApi('schedule', parameters); return requestFromAPI(route, { request, options: { method: 'DELETE' }, @@ -71,11 +104,13 @@ export async function deleteSchedule( type CreateScheduleOptions = { namespace: string; + scheduleId: string; body: CreateScheduleRequest; }; export async function createSchedule({ namespace, + scheduleId, body, }: CreateScheduleOptions): Promise<{ error: string; conflictToken: string }> { let error = ''; @@ -84,8 +119,9 @@ export async function createSchedule({ err?.body?.message ?? `Error creating schedule: ${err.status}: ${err.statusText}`); - const route = await routeForApi('schedules', { + const route = routeForApi('schedule', { namespace, + scheduleId, }); const { conflictToken } = await requestFromAPI<{ conflictToken: string }>( route, @@ -97,7 +133,6 @@ export async function createSchedule({ ...body, }), }, - shouldRetry: false, onError, }, ); @@ -123,7 +158,7 @@ export async function editSchedule({ err?.body?.message ?? `Error editing schedule: ${err.status}: ${err.statusText}`); - const route = await routeForApi('schedule', { + const route = routeForApi('schedule.edit', { namespace, scheduleId, }); @@ -135,7 +170,6 @@ export async function editSchedule({ ...body, }), }, - shouldRetry: false, onError, }); @@ -159,19 +193,18 @@ export async function pauseSchedule({ }, }; - const route = await routeForApi('schedule', { + const route = routeForApi('schedule.patch', { namespace, scheduleId: scheduleId, }); return await requestFromAPI(route, { options: { - method: 'PATCH', + method: 'POST', body: stringifyWithBigInt({ ...options, request_id: uuidv4(), }), }, - shouldRetry: false, onError: (error) => console.error(error), }); } @@ -193,18 +226,90 @@ export async function unpauseSchedule({ }, }; - const route = await routeForApi('schedule', { + const route = routeForApi('schedule.patch', { namespace, scheduleId: scheduleId, }); return await requestFromAPI(route, { options: { - method: 'PATCH', + method: 'POST', + body: stringifyWithBigInt({ + ...options, + request_id: uuidv4(), + }), + }, + }); +} + +type TriggerImmediatelyOptions = { + namespace: string; + scheduleId: string; + overlapPolicy: OverlapPolicy; +}; + +export async function triggerImmediately({ + namespace, + scheduleId, + overlapPolicy, +}: TriggerImmediatelyOptions): Promise { + const options = { + patch: { + triggerImmediately: { + overlapPolicy, + }, + }, + }; + + const route = routeForApi('schedule.patch', { + namespace, + scheduleId: scheduleId, + }); + return await requestFromAPI(route, { + options: { + method: 'POST', + body: stringifyWithBigInt({ + ...options, + request_id: uuidv4(), + }), + }, + }); +} + +type BackfillOptions = TriggerImmediatelyOptions & { + startTime: string; + endTime: string; +}; + +export async function backfillRequest({ + namespace, + scheduleId, + overlapPolicy, + startTime, + endTime, +}: BackfillOptions): Promise { + const options = { + patch: { + backfillRequest: [ + { + overlapPolicy, + startTime, + endTime, + }, + ], + }, + }; + + const route = routeForApi('schedule.patch', { + namespace, + scheduleId: scheduleId, + }); + return await requestFromAPI(route, { + options: { + method: 'POST', body: stringifyWithBigInt({ ...options, request_id: uuidv4(), }), }, - shouldRetry: false, }); } diff --git a/src/lib/services/search-attributes-service.ts b/src/lib/services/search-attributes-service.ts index 79e395293..5f560e219 100644 --- a/src/lib/services/search-attributes-service.ts +++ b/src/lib/services/search-attributes-service.ts @@ -1,21 +1,40 @@ -import { searchAttributes } from '$lib/stores/search-attributes'; +import type { SearchAttributesResponse } from '$lib/types/workflows'; import { requestFromAPI } from '$lib/utilities/request-from-api'; import { routeForApi } from '$lib/utilities/route-for-api'; +import { toSearchAttributeTypeReadable } from '$lib/utilities/screaming-enums'; -export const fetchSearchAttributes = async ( - settings: Settings, +export const fetchSearchAttributesForNamespace = async ( + namespace: string, request = fetch, -): Promise => { - if (settings.runtimeEnvironment.isCloud) return; +): Promise> => { + try { + const route = routeForApi('search-attributes', { namespace }); + const searchAttributesResponse = + await requestFromAPI(route, { + request, + }); - const route = await routeForApi('search-attributes'); - return await requestFromAPI(route, { - request, - }).then((searchAttributesResponse) => { - if (searchAttributesResponse?.keys) { - searchAttributes.set(searchAttributesResponse.keys); - } - - return searchAttributesResponse; - }); + const customAttributes = { ...searchAttributesResponse.customAttributes }; + const systemAttributes = { ...searchAttributesResponse.systemAttributes }; + Object.entries(customAttributes).forEach(([key, value]) => { + customAttributes[key] = toSearchAttributeTypeReadable(value); + }); + Object.entries(systemAttributes).forEach(([key, value]) => { + systemAttributes[key] = toSearchAttributeTypeReadable(value); + }); + return { + customAttributes, + systemAttributes, + }; + } catch (e) { + console.error( + 'Error fetching search attributes for namespace', + namespace, + e, + ); + return { + customAttributes: {}, + systemAttributes: {}, + }; + } }; diff --git a/src/lib/services/settings-service.test.ts b/src/lib/services/settings-service.test.ts index 254c7f5d3..8fd301d98 100644 --- a/src/lib/services/settings-service.test.ts +++ b/src/lib/services/settings-service.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { isCloudMatch } from './settings-service'; describe('isCloudMatch', () => { diff --git a/src/lib/services/settings-service.ts b/src/lib/services/settings-service.ts index d01252cc4..7f40e3a0c 100644 --- a/src/lib/services/settings-service.ts +++ b/src/lib/services/settings-service.ts @@ -1,15 +1,16 @@ -import { browser } from '$app/env'; -import { settings } from '$lib/stores/settings'; +import { BROWSER } from 'esm-env'; + +import type { SettingsResponse } from '$lib/types'; +import type { Settings } from '$lib/types/global'; import { getApiOrigin } from '$lib/utilities/get-api-origin'; import { getEnvironment } from '$lib/utilities/get-environment'; import { requestFromAPI } from '$lib/utilities/request-from-api'; import { routeForApi } from '$lib/utilities/route-for-api'; -import type { SettingsResponse } from '$types'; export const isCloudMatch = /(tmprl\.cloud|tmprl-test\.cloud)$/; export const fetchSettings = async (request = fetch): Promise => { - const route = await routeForApi('settings'); + const route = routeForApi('settings'); const settingsResponse: SettingsResponse = await requestFromAPI(route, { request, }); @@ -21,13 +22,33 @@ export const fetchSettings = async (request = fetch): Promise => { enabled: !!settingsResponse?.Auth?.Enabled, options: settingsResponse?.Auth?.Options, }, + bannerText: settingsResponse?.BannerText, baseUrl: getApiOrigin(), codec: { endpoint: settingsResponse?.Codec?.Endpoint, passAccessToken: settingsResponse?.Codec?.PassAccessToken, + includeCredentials: settingsResponse?.Codec?.IncludeCredentials, + customErrorMessage: { + default: { + message: settingsResponse?.Codec?.DefaultErrorMessage || '', + link: settingsResponse?.Codec?.DefaultErrorLink || '', + }, + }, }, defaultNamespace: settingsResponse?.DefaultNamespace || 'default', // API returns an empty string if default namespace is not configured disableWriteActions: !!settingsResponse?.DisableWriteActions || false, + workflowTerminateDisabled: !!settingsResponse?.WorkflowTerminateDisabled, + workflowCancelDisabled: !!settingsResponse?.WorkflowCancelDisabled, + workflowSignalDisabled: !!settingsResponse?.WorkflowSignalDisabled, + workflowUpdateDisabled: !!settingsResponse?.WorkflowUpdateDisabled, + workflowResetDisabled: !!settingsResponse?.WorkflowResetDisabled, + batchActionsDisabled: !!settingsResponse?.BatchActionsDisabled, + startWorkflowDisabled: !!settingsResponse?.StartWorkflowDisabled, + hideWorkflowQueryErrors: !!settingsResponse?.HideWorkflowQueryErrors, + refreshWorkflowCountsDisabled: + !!settingsResponse?.RefreshWorkflowCountsDisabled, + activityCommandsDisabled: !!settingsResponse?.ActivityCommandsDisabled, + showTemporalSystemNamespace: settingsResponse?.ShowTemporalSystemNamespace, notifyOnNewVersion: settingsResponse?.NotifyOnNewVersion, feedbackURL: settingsResponse?.FeedbackURL, @@ -37,21 +58,19 @@ export const fetchSettings = async (request = fetch): Promise => { return EnvironmentOverride === 'cloud'; } - return isCloudMatch.test(browser ? window.location.hostname : ''); + return isCloudMatch.test(BROWSER ? window.location.hostname : ''); }, get isLocal() { if (EnvironmentOverride) { return EnvironmentOverride === 'local'; } - return isCloudMatch.test(browser ? window.location.hostname : ''); + return isCloudMatch.test(BROWSER ? window.location.hostname : ''); }, envOverride: Boolean(EnvironmentOverride), }, version: settingsResponse?.Version, }; - settings.set(settingsInformation); - return settingsInformation; }; diff --git a/src/lib/services/terminate-service.ts b/src/lib/services/terminate-service.ts deleted file mode 100644 index 962ab485b..000000000 --- a/src/lib/services/terminate-service.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { v4 as uuidv4 } from 'uuid'; -import { stringifyWithBigInt } from '$lib/utilities/parse-with-big-int'; -import { requestFromAPI } from '$lib/utilities/request-from-api'; -import { routeForApi } from '$lib/utilities/route-for-api'; - -type TerminateWorkflowOptions = { - workflow: WorkflowExecution; - namespace: string; - reason: string; -}; - -export async function terminateWorkflow({ - workflow, - namespace, - reason, -}: TerminateWorkflowOptions): Promise { - const route = await routeForApi('workflow.terminate', { - namespace, - workflowId: workflow.id, - runId: workflow.runId, - }); - return await requestFromAPI(route, { - options: { method: 'POST', body: stringifyWithBigInt({ reason }) }, - shouldRetry: false, - notifyOnError: false, - }); -} - -type BulkTerminateWorkflowOptions = { - namespace: string; - workflowExecutions: WorkflowExecution[]; - reason: string; -}; - -export async function batchTerminateWorkflows({ - namespace, - workflowExecutions, - reason, -}: BulkTerminateWorkflowOptions): Promise { - const route = await routeForApi('workflows.batch.terminate', { namespace }); - - const runIds = workflowExecutions.map((wf) => wf.runId); - const query = runIds.reduce((queryString, id, index, arr) => { - queryString += `RunId="${id}"`; - if (index !== arr.length - 1) { - queryString += ' OR '; - } - - return queryString; - }, ''); - - const jobId = uuidv4(); - - await requestFromAPI(route, { - options: { - method: 'POST', - body: stringifyWithBigInt({ - jobId, - namespace, - visibilityQuery: query, - reason, - terminationOperation: {}, - }), - }, - shouldRetry: false, - notifyOnError: false, - }); - - return jobId; -} - -type DescribeBatchOperationOptions = { - jobId: string; - namespace: string; -}; - -export async function pollBatchOperation({ - namespace, - jobId, -}: DescribeBatchOperationOptions) { - return new Promise((resolve, reject) => { - describeBatchOperation({ namespace, jobId }).then(({ state }) => { - if (state === 'Failed') { - reject(); - } else if (state !== 'Running') { - resolve(); - } else { - setTimeout(async () => { - try { - resolve(pollBatchOperation({ namespace, jobId })); - } catch { - reject(); - } - }, 1000); - } - }); - }); -} - -async function describeBatchOperation({ - jobId, - namespace, -}: DescribeBatchOperationOptions): Promise { - const route = await routeForApi('workflows.batch.describe', { namespace }); - - return requestFromAPI(route, { - params: { jobId }, - }); -} diff --git a/src/lib/services/workflow-activities-service.ts b/src/lib/services/workflow-activities-service.ts new file mode 100644 index 000000000..636954caf --- /dev/null +++ b/src/lib/services/workflow-activities-service.ts @@ -0,0 +1,187 @@ +import { get } from 'svelte/store'; + +import { authUser } from '$lib/stores/auth-user'; +import type { + ActivityPauseRequest, + ActivityPauseResponse, + ActivityResetRequest, + ActivityResetResponse, + ActivityUnpauseRequest, + ActivityUnpauseResponse, + ActivityUpdateOptionsRequest, + ActivityUpdateOptionsResponse, +} from '$lib/types'; +import type { + CompleteActivityTaskRequest, + CompleteActivityTaskResponse, + FailActivityTaskRequest, + FailActivityTaskResponse, +} from '$lib/types/events'; +import { stringifyWithBigInt } from '$lib/utilities/parse-with-big-int'; +import { requestFromAPI } from '$lib/utilities/request-from-api'; +import { routeForApi } from '$lib/utilities/route-for-api'; + +type WorkflowInformation = { + workflowId: string; + runId: string; + activityId: string; +}; + +export const failActivityTask = async ({ + namespace, + workflowId, + runId, + activityId, + failure, + identity, + lastHeartbeatDetails, +}: FailActivityTaskRequest & + WorkflowInformation): Promise => { + const route = routeForApi('activity.fail', { + namespace, + }); + return requestFromAPI(route, { + notifyOnError: false, + options: { + body: stringifyWithBigInt({ failure, identity, lastHeartbeatDetails }), + }, + params: { + workflowId, + runId, + activityId, + }, + }); +}; + +export const completeActivityTask = async ({ + namespace, + workflowId, + runId, + activityId, + identity, + result, +}: CompleteActivityTaskRequest & + WorkflowInformation): Promise => { + const route = routeForApi('activity.complete', { + namespace, + }); + + return requestFromAPI(route, { + notifyOnError: false, + options: { body: stringifyWithBigInt({ identity, result }) }, + params: { + workflowId, + runId, + activityId, + }, + }); +}; + +const getIdentity = () => { + const email = get(authUser)?.email; + const identity = email ? `From the Web UI by ${email}` : 'From the Web UI'; + return identity; +}; + +export const pauseActivity = async ({ + namespace, + execution, + id, + reason, + type, +}: ActivityPauseRequest & { + reason?: string; +}): Promise => { + const route = routeForApi('activity.pause', { + namespace, + }); + + return requestFromAPI(route, { + options: { + method: 'POST', + body: stringifyWithBigInt({ + execution, + reason, + identity: getIdentity(), + id, + type, + }), + }, + }); +}; + +export const unpauseActivity = async ({ + namespace, + execution, + id, + type, +}: ActivityUnpauseRequest): Promise => { + const route = routeForApi('activity.unpause', { + namespace, + }); + + return requestFromAPI(route, { + options: { + method: 'POST', + body: stringifyWithBigInt({ + execution, + identity: getIdentity(), + id, + type, + }), + }, + }); +}; + +export const resetActivity = async ({ + namespace, + execution, + id, + type, + resetHeartbeat, +}: ActivityResetRequest): Promise => { + const route = routeForApi('activity.reset', { + namespace, + }); + + return requestFromAPI(route, { + options: { + method: 'POST', + body: stringifyWithBigInt({ + execution, + identity: getIdentity(), + id, + type, + resetHeartbeat, + }), + }, + }); +}; + +export const updateActivityOptions = async ({ + namespace, + execution, + id, + type, + activityOptions, +}: ActivityUpdateOptionsRequest): Promise => { + const route = routeForApi('activity.update-options', { + namespace, + }); + + const fullMask = + 'taskQueue.name,scheduleToCloseTimeout,scheduleToStartTimeout,startToCloseTimeout,heartbeatTimeout,retryPolicy.initialInterval,retryPolicy.backoffCoefficient,retryPolicy.maximumInterval,retryPolicy.maximumAttempts'; + return requestFromAPI(route, { + options: { + method: 'POST', + body: stringifyWithBigInt({ + execution, + identity: getIdentity(), + id, + type, + activityOptions, + updateMask: fullMask, + }), + }, + }); +}; diff --git a/src/lib/services/workflow-counts.ts b/src/lib/services/workflow-counts.ts new file mode 100644 index 000000000..79c3cdd9c --- /dev/null +++ b/src/lib/services/workflow-counts.ts @@ -0,0 +1,76 @@ +import type { CountWorkflowExecutionsResponse } from '$lib/types/workflows'; +import { requestFromAPI } from '$lib/utilities/request-from-api'; +import { routeForApi } from '$lib/utilities/route-for-api'; + +export const fetchWorkflowCount = async ( + namespace: string, + query: string, + request = fetch, +): Promise<{ count: number }> => { + let count = 0; + try { + const countRoute = routeForApi('workflows.count', { namespace }); + const result = await requestFromAPI<{ count: string }>(countRoute, { + params: query ? { query } : {}, + onError: () => {}, + handleError: () => {}, + request, + }); + count = parseInt(result?.count || '0'); + } catch (e) { + // Don't fail the workflows call due to count + } + + return { count }; +}; + +type WorkflowCountByExecutionStatusOptions = { + namespace: string; + query: string; +}; + +export const fetchWorkflowCountByExecutionStatus = async ({ + namespace, + query, +}: WorkflowCountByExecutionStatusOptions): Promise => { + const groupByClause = 'GROUP BY ExecutionStatus'; + const countRoute = routeForApi('workflows.count', { + namespace, + }); + const { count, groups } = + await requestFromAPI(countRoute, { + params: { + query: query ? `${query} ${groupByClause}` : `${groupByClause}`, + }, + notifyOnError: false, + }); + return { count: count ?? '0', groups }; +}; + +export const fetchScheduleCount = async ({ + namespace, + query, +}: { + namespace: string; + query?: string; +}): Promise => { + const scheduleFixedQuery = + 'TemporalNamespaceDivision="TemporalScheduler" AND ExecutionStatus="Running"'; + + const fullQuery = query + ? `${scheduleFixedQuery} AND ${query}` + : scheduleFixedQuery; + const countRoute = routeForApi('workflows.count', { + namespace, + }); + const { count } = await requestFromAPI( + countRoute, + { + params: { + query: fullQuery, + }, + notifyOnError: false, + }, + ); + return count ?? '0'; +}; diff --git a/src/lib/services/workflow-service.test.ts b/src/lib/services/workflow-service.test.ts new file mode 100644 index 000000000..6c77a0e60 --- /dev/null +++ b/src/lib/services/workflow-service.test.ts @@ -0,0 +1,62 @@ +import { afterEach, describe, expect, test, vi } from 'vitest'; + +import { fetchAllWorkflows, fetchWorkflowForRunId } from './workflow-service'; +import { requestFromAPI } from '../utilities/request-from-api'; + +vi.mock('../utilities/request-from-api', () => ({ + requestFromAPI: vi.fn().mockImplementation( + () => + new Promise((resolve) => + resolve({ + executions: [], + nextPageToken: '', + }), + ), + ), +})); + +describe('workflow service', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('fetchAllWorkflows', () => { + test('preserves queries with "%"', async () => { + await fetchAllWorkflows('test', { + query: 'WorkflowType LIKE "cron%"', + }); + + expect(requestFromAPI).toHaveBeenCalledOnce(); + expect(requestFromAPI).toHaveBeenCalledWith( + 'http://localhost:8233/api/v1/namespaces/test/workflows', + { + handleError: expect.any(Function), + onError: expect.any(Function), + params: { + query: 'WorkflowType LIKE "cron%"', + }, + request: expect.any(Function), + }, + ); + }); + }); + + describe('fetchWorkflowForRunId', () => { + test('is called with the correct params', async () => { + const workflowId = 'temporal.test%'; + await fetchWorkflowForRunId({ namespace: 'test', workflowId }); + + expect(requestFromAPI).toHaveBeenCalledOnce(); + expect(requestFromAPI).toHaveBeenCalledWith( + 'http://localhost:8233/api/v1/namespaces/test/workflows', + { + params: { + query: `WorkflowId="${workflowId}"`, + pageSize: '1', + }, + request: expect.any(Function), + }, + ); + }); + }); +}); diff --git a/src/lib/services/workflow-service.ts b/src/lib/services/workflow-service.ts index 19bf8f744..c92dbb514 100644 --- a/src/lib/services/workflow-service.ts +++ b/src/lib/services/workflow-service.ts @@ -1,23 +1,84 @@ -import type { ErrorCallback } from '$lib/utilities/request-from-api'; +import { get } from 'svelte/store'; + +import { v4 } from 'uuid'; + +import { page } from '$app/stores'; +import { + isPayloadInputEncodingType, + type PayloadInputEncoding, +} from '$lib/components/payload-input-with-encoding.svelte'; +import { translate } from '$lib/i18n/translate'; +import { Action } from '$lib/models/workflow-actions'; import { toWorkflowExecution, toWorkflowExecutions, } from '$lib/models/workflow-execution'; - -import { requestFromAPI } from '$lib/utilities/request-from-api'; -import { routeForApi } from '$lib/utilities/route-for-api'; -import { toListWorkflowQuery } from '$lib/utilities/query/list-workflow-query'; +import { isCloud } from '$lib/stores/advanced-visibility'; +import { authUser } from '$lib/stores/auth-user'; +import type { SearchAttributeInput } from '$lib/stores/search-attributes'; +import { temporalVersion } from '$lib/stores/versions'; +import { + canFetchChildWorkflows, + hideWorkflowQueryErrors, + workflowError, +} from '$lib/stores/workflows'; +import { + ResetReapplyExcludeType, + ResetReapplyType, + type ResetWorkflowRequest, + type SearchAttribute, + type UpdateWorkflowResponse, +} from '$lib/types'; +import type { + ValidWorkflowEndpoints, + ValidWorkflowParameters, +} from '$lib/types/api'; +import type { Payload, WorkflowExecutionStartedEvent } from '$lib/types/events'; +import type { + NamespaceScopedRequest, + NetworkError, + Replace, +} from '$lib/types/global'; +import type { + ArchiveFilterParameters, + CountWorkflowExecutionsResponse, + ListWorkflowExecutionsResponse, + WorkflowExecution, + WorkflowIdentifier, +} from '$lib/types/workflows'; +import { + cloneAllPotentialPayloadsWithCodec, + decodeSingleReadablePayloadWithCodec, + type PotentiallyDecodable, +} from '$lib/utilities/decode-payload'; +import { + encodePayloads, + setBase64Payload, +} from '$lib/utilities/encode-payload'; import { handleUnauthorizedOrForbiddenError, isForbidden, isUnauthorized, } from '$lib/utilities/handle-error'; -import { noop } from 'svelte/internal'; +import { paginated } from '$lib/utilities/paginated'; +import { stringifyWithBigInt } from '$lib/utilities/parse-with-big-int'; +import { toListWorkflowQuery } from '$lib/utilities/query/list-workflow-query'; +import type { ErrorCallback } from '$lib/utilities/request-from-api'; +import { requestFromAPI } from '$lib/utilities/request-from-api'; +import { base, pathForApi, routeForApi } from '$lib/utilities/route-for-api'; +import { + isVersionNewer, + minimumVersionRequired, +} from '$lib/utilities/version-check'; +import { formatReason } from '$lib/utilities/workflow-actions'; + +import { fetchInitialEvent } from './events-service'; +import { fetchWorkflowCountByExecutionStatus } from './workflow-counts'; export type GetWorkflowExecutionRequest = NamespaceScopedRequest & { workflowId: string; - runId: string; + runId?: string; }; export type CombinedWorkflowExecutionsResponse = { @@ -26,73 +87,80 @@ export type CombinedWorkflowExecutionsResponse = { error?: string; }; -type CancelWorkflowExecutionParameters = { +type CancelWorkflowOptions = { + namespace: string; + workflow: WorkflowExecution; +}; + +type SignalWorkflowOptions = { + namespace: string; + workflow: WorkflowExecution; + name: string; + input: string; + encoding: PayloadInputEncoding; + messageType: string; +}; + +type UpdateWorkflowOptions = { + namespace: string; + workflow: WorkflowIdentifier; + name: string; + identity?: string; + input: string; + updateId?: string; + encoding: PayloadInputEncoding; +}; + +type StartWorkflowOptions = { namespace: string; workflowId: string; - runId: string; + taskQueue: string; + workflowType: string; + input: string; + encoding: PayloadInputEncoding; + messageType: string; + summary: string; + details: string; + searchAttributes: SearchAttributeInput[]; +}; + +type TerminateWorkflowOptions = { + workflow: WorkflowExecution; + namespace: string; + reason: string; + first: string | undefined; +}; + +export type ResetWorkflowOptions = { + namespace: string; + workflow: WorkflowExecution; + eventId: string; + reason: string; + // used pre temporal server v1.24 + includeSignals: boolean; + // used post temporal server v1.24 + excludeSignals: boolean; + excludeUpdates: boolean; }; export type FetchWorkflow = | typeof fetchAllWorkflows | typeof fetchAllArchivedWorkflows; -export const fetchWorkflowCount = async ( - namespace: string, - query: string, - request = fetch, -): Promise<{ totalCount: number; count: number }> => { - let totalCount = 0; - let count = 0; - try { - const countRoute = await routeForApi('workflows.count', { namespace }); - - if (!query) { - const totalCountResult = await requestFromAPI<{ count: number }>( - countRoute, - { - params: { query }, - onError: noop, - handleError: noop, - request, - }, - ); - totalCount = totalCountResult?.count; - } else { - const countPromise = requestFromAPI<{ count: number }>(countRoute, { - params: { query }, - onError: noop, - handleError: noop, - request, - }); - const totalCountPromise = requestFromAPI<{ count: number }>(countRoute, { - params: { query: '' }, - onError: noop, - handleError: noop, - request, - }); - const [countResult, totalCountResult] = await Promise.all([ - countPromise, - totalCountPromise, - ]); - count = countResult?.count; - totalCount = totalCountResult?.count; - } - } catch (e) { - // Don't fail the workflows call due to count - } - - return { count, totalCount }; -}; - export const fetchAllWorkflows = async ( namespace: string, parameters: ValidWorkflowParameters, request = fetch, archived = false, ): Promise => { - const query = decodeURIComponent( - parameters.query || toListWorkflowQuery(parameters, archived), - ); + const rawQuery = + parameters.query || toListWorkflowQuery(parameters, archived); + let query: string; + try { + query = decodeURIComponent(rawQuery); + } catch { + query = rawQuery; + } const endpoint: ValidWorkflowEndpoints = archived ? 'workflows.archived' @@ -107,11 +175,11 @@ export const fetchAllWorkflows = async ( err?.body?.message ?? `Error fetching workflows: ${err.status}: ${err.statusText}`; } else { - error = `Error fetching workflows: Server failed to respond`; + error = 'Error fetching workflows: Server failed to respond'; } }; - const route = await routeForApi(endpoint, { namespace }); + const route = routeForApi(endpoint, { namespace }); const { executions, nextPageToken } = (await requestFromAPI(route, { params: { query }, @@ -127,6 +195,37 @@ export const fetchAllWorkflows = async ( }; }; +type WorkflowForRunIdParams = { + namespace: string; + workflowId: string; + url?: string; +}; + +export const fetchWorkflowForRunId = async ( + { namespace, workflowId, url }: WorkflowForRunIdParams, + request = fetch, +): Promise<{ runId: string }> => { + const endpoint: ValidWorkflowEndpoints = 'workflows'; + const baseUrl = url ?? base(namespace); + const path = pathForApi(endpoint, { namespace }); + const route = baseUrl + path; + const { executions } = (await requestFromAPI( + route, + { + params: { + query: `WorkflowId="${workflowId}"`, + pageSize: '1', + }, + request, + }, + )) ?? { executions: [] }; + const latestExecution = toWorkflowExecutions({ executions })?.[0]; + + return { + runId: latestExecution?.runId, + }; +}; + export const fetchWorkflowForAuthorization = async ( namespace: string, request = fetch, @@ -143,7 +242,7 @@ export const fetchWorkflowForAuthorization = async ( } }; - const route = await routeForApi(endpoint, { namespace }); + const route = routeForApi(endpoint, { namespace }); await requestFromAPI(route, { params: { pageSize: '1' }, onError, @@ -167,19 +266,75 @@ export const fetchAllArchivedWorkflows = async ( export async function fetchWorkflow( parameters: GetWorkflowExecutionRequest, request = fetch, -): Promise { - const route = await routeForApi('workflow', parameters); - return requestFromAPI(route, { request }).then(toWorkflowExecution); +): Promise<{ + workflow?: WorkflowExecution; + error?: NetworkError; +}> { + const route = routeForApi('workflow', { + namespace: parameters.namespace, + workflowId: parameters.workflowId, + }); + + return requestFromAPI(route, { + request, + notifyOnError: false, + params: parameters.runId + ? { + 'execution.runId': parameters.runId, + } + : {}, + }) + .then((response) => { + return { workflow: toWorkflowExecution(response) }; + }) + .catch((e: NetworkError) => { + return { error: e }; + }); +} + +export async function terminateWorkflow({ + workflow, + namespace, + reason, + first, +}: TerminateWorkflowOptions): Promise { + const route = routeForApi('workflow.terminate', { + namespace, + workflowId: workflow.id, + }); + + const email = get(authUser).email; + const formattedReason = formatReason({ + reason, + action: Action.Terminate, + email, + }); + + return await requestFromAPI(route, { + options: { + method: 'POST', + body: stringifyWithBigInt({ + reason: formattedReason, + ...(email && { identity: email }), + firstExecutionRunId: first, + }), + }, + notifyOnError: false, + params: first + ? {} + : { + 'execution.runId': workflow.runId, + }, + }); } -export const cancelWorkflow = async ( - { namespace, workflowId, runId }: CancelWorkflowExecutionParameters, +export async function cancelWorkflow( + { namespace, workflow: { id: workflowId, runId } }: CancelWorkflowOptions, request = fetch, -) => { - const route = await routeForApi('workflow.cancel', { +) { + const route = routeForApi('workflow.cancel', { namespace, workflowId, - runId, }); return requestFromAPI(route, { @@ -188,8 +343,181 @@ export const cancelWorkflow = async ( options: { method: 'POST', }, + params: { + 'execution.runId': runId, + }, + }); +} + +export async function signalWorkflow({ + namespace, + workflow: { id: workflowId, runId }, + name, + input, + encoding, + messageType, +}: SignalWorkflowOptions) { + const route = routeForApi('workflow.signal', { + namespace, + workflowId, + signalName: name, + }); + const payloads = await encodePayloads({ input, encoding, messageType }); + const settings = get(page).data.settings; + const version = settings?.version ?? ''; + const newVersion = isVersionNewer(version, '2.22'); + const body = newVersion + ? { + signalName: name, + workflowExecution: { + workflowId, + runId, + }, + input: { + payloads, + }, + } + : { + signalName: name, + input: { + payloads, + }, + params: { + 'execution.runId': runId, + }, + }; + + return requestFromAPI(route, { + notifyOnError: false, + options: { + method: 'POST', + body: stringifyWithBigInt(body), + }, + }); +} + +export async function updateWorkflow({ + namespace, + workflow: { workflowId, runId }, + name, + identity, + updateId, + input = '', + encoding, +}: UpdateWorkflowOptions): Promise { + const route = routeForApi('workflow.update', { + namespace, + workflowId, + updateName: name, + }); + const payloads = await encodePayloads({ input, encoding }); + const body = { + workflowExecution: { + runId, + }, + request: { + meta: { + updateId, + identity, + }, + input: { + args: { + payloads, + }, + }, + }, + }; + + return requestFromAPI(route, { + notifyOnError: false, + options: { + method: 'POST', + body: stringifyWithBigInt(body), + }, + }); +} + +export async function resetWorkflow({ + namespace, + workflow: { id: workflowId, runId }, + eventId, + reason, + includeSignals, + excludeSignals, + excludeUpdates, +}: ResetWorkflowOptions): Promise<{ runId: string }> { + const route = routeForApi('workflow.reset', { + namespace, + workflowId, }); -}; + + const email = get(authUser).email; + const formattedReason = formatReason({ + action: Action.Reset, + reason, + email, + }); + + const body: Replace< + ResetWorkflowRequest, + { workflowTaskFinishEventId: string } + > = { + workflowExecution: { + workflowId, + runId, + }, + workflowTaskFinishEventId: eventId, + requestId: v4(), + reason: formattedReason, + }; + + if (get(isCloud) || minimumVersionRequired('1.24.0', get(temporalVersion))) { + const resetReapplyExcludeTypes: ResetWorkflowRequest['resetReapplyExcludeTypes'] = + []; + + if (!excludeSignals && !excludeUpdates) { + resetReapplyExcludeTypes.push( + ResetReapplyExcludeType.RESET_REAPPLY_EXCLUDE_TYPE_UNSPECIFIED, + ); + } + + if (excludeSignals) { + resetReapplyExcludeTypes.push( + ResetReapplyExcludeType.RESET_REAPPLY_EXCLUDE_TYPE_SIGNAL, + ); + } + + if (excludeUpdates) { + resetReapplyExcludeTypes.push( + ResetReapplyExcludeType.RESET_REAPPLY_EXCLUDE_TYPE_UPDATE, + ); + } + + body.resetReapplyExcludeTypes = resetReapplyExcludeTypes; + body.resetReapplyType = ResetReapplyType.RESET_REAPPLY_TYPE_ALL_ELIGIBLE; + } else { + let resetReapplyType: ResetWorkflowRequest['resetReapplyType']; + + if (includeSignals) { + resetReapplyType = ResetReapplyType.RESET_REAPPLY_TYPE_SIGNAL; + } else { + resetReapplyType = ResetReapplyType.RESET_REAPPLY_TYPE_NONE; + } + + body.resetReapplyType = resetReapplyType; + } + + return requestFromAPI<{ runId: string }>(route, { + notifyOnError: false, + options: { + method: 'POST', + body: stringifyWithBigInt(body), + }, + params: { + 'execution.runId': runId, + }, + }); +} export async function fetchWorkflowForSchedule( parameters: GetWorkflowExecutionRequest, @@ -199,10 +527,489 @@ export async function fetchWorkflowForSchedule( console.error(err); }; - const route = await routeForApi('workflow', parameters); + const route = routeForApi('workflow', parameters); return requestFromAPI(route, { request, onError, handleError: onError, }).then(toWorkflowExecution); } + +export async function fetchAllChildWorkflows( + namespace: string, + workflowId: string, + runId?: string, +): Promise { + if (!get(canFetchChildWorkflows)) { + return []; + } + try { + let query = `ParentWorkflowId = "${workflowId}"`; + if (runId) { + query += ` AND ParentRunId = "${runId}"`; + } + const { workflows } = await fetchAllWorkflows(namespace, { query }); + return workflows; + } catch (e) { + return []; + } +} + +export const setSearchAttributes = ( + attributes: SearchAttributeInput[], +): SearchAttribute => { + if (!attributes.length) return {}; + + const searchAttributes: SearchAttribute = {}; + attributes.forEach((attribute) => { + searchAttributes[attribute.label] = setBase64Payload(attribute.value); + }); + + return searchAttributes; +}; + +export async function startWorkflow({ + namespace, + workflowId, + taskQueue, + workflowType, + input, + summary, + details, + encoding, + messageType, + searchAttributes, +}: StartWorkflowOptions): Promise<{ runId: string }> { + const route = routeForApi('workflow', { + namespace, + workflowId, + }); + let payloads; + let summaryPayload; + let detailsPayload; + + if (input) { + try { + payloads = await encodePayloads({ input, encoding, messageType }); + } catch (_) { + throw new Error('Could not encode input for starting workflow'); + } + } + + try { + if (summary) { + summaryPayload = ( + await encodePayloads({ + input: stringifyWithBigInt(summary), + encoding: 'json/plain', + }) + )[0]; + } + + if (details) { + detailsPayload = ( + await encodePayloads({ + input: stringifyWithBigInt(details), + encoding: 'json/plain', + }) + )[0]; + } + } catch (e) { + console.error('Could not encode summary or details for starting workflow'); + } + + const body = stringifyWithBigInt({ + workflowId, + taskQueue: { + name: taskQueue, + }, + workflowType: { + name: workflowType, + }, + input: payloads ? { payloads } : null, + userMetadata: { + summary: summaryPayload, + details: detailsPayload, + }, + searchAttributes: + searchAttributes.length === 0 + ? null + : { + indexedFields: { + ...setSearchAttributes(searchAttributes), + }, + }, + }); + + return requestFromAPI(route, { + notifyOnError: false, + options: { + method: 'POST', + body, + }, + }); +} + +export const fetchInitialValuesForStartWorkflow = async ({ + namespace, + workflowType, + workflowId, +}: { + namespace: string; + workflowType?: string; + workflowId?: string; +}): Promise<{ + input: string; + encoding: PayloadInputEncoding; + messageType: string; + searchAttributes: Record | undefined; + summary: string; + details: string; +}> => { + const handleError: ErrorCallback = (err) => { + console.error(err); + }; + const emptyValues = { + input: '', + encoding: 'json/plain' as PayloadInputEncoding, + messageType: '', + searchAttributes: undefined, + summary: '', + details: '', + }; + try { + let query = ''; + if (workflowType && workflowId) { + query = `WorkflowType = "${workflowType}" AND WorkflowId = "${workflowId}"`; + } else if (workflowType) { + query = `WorkflowType = "${workflowType}"`; + } else if (workflowId) { + query = `WorkflowId = "${workflowId}"`; + } + + const route = routeForApi('workflows', { namespace }); + const workflows = await requestFromAPI( + route, + { + params: { query, pageSize: '1' }, + handleError, + }, + ); + + if (!workflows?.executions?.[0]) return emptyValues; + const listWorkflow = toWorkflowExecutions(workflows)[0]; + const params = { + namespace, + workflowId: listWorkflow.id, + runId: listWorkflow.runId, + }; + const { workflow } = await fetchWorkflow(params); + const firstEvent = await fetchInitialEvent(params); + + const startEvent = firstEvent as WorkflowExecutionStartedEvent; + const convertedAttributes = (await cloneAllPotentialPayloadsWithCodec( + startEvent?.attributes?.input, + namespace, + get(page).data.settings, + get(authUser).accessToken, + 'readable', + false, + )) as PotentiallyDecodable; + + let summary = ''; + if (workflow.summary) { + const decodedSummary = await decodeSingleReadablePayloadWithCodec( + workflow.summary, + ); + if (typeof decodedSummary === 'string') { + summary = decodedSummary; + } + } + + let details = ''; + if (workflow.details) { + const decodedDetails = await decodeSingleReadablePayloadWithCodec( + workflow.details, + ); + if (typeof decodedDetails === 'string') { + details = decodedDetails; + } + } + const input = convertedAttributes?.payloads + ? stringifyWithBigInt(convertedAttributes.payloads[0]?.data) + : ''; + const encoding = + convertedAttributes?.payloads && + isPayloadInputEncodingType( + convertedAttributes.payloads[0]?.metadata?.encoding, + ) + ? convertedAttributes.payloads[0]?.metadata?.encoding + : 'json/plain'; + const messageType = convertedAttributes?.payloads + ? convertedAttributes.payloads[0]?.metadata?.messageType + : ''; + + return { + input, + encoding, + messageType, + searchAttributes: workflow?.searchAttributes?.indexedFields, + summary, + details, + }; + } catch (e) { + return emptyValues; + } +}; + +export interface RootNode { + workflow: WorkflowExecution; + children: RootNode[]; + siblingCount?: number; + scheduleId?: string; + rootPaths: { runId: string; workflowId: string }[]; +} + +const buildRoots = ( + root: WorkflowExecution, + workflows: WorkflowExecution[], +) => { + const childrenMap = new Map(); + + workflows.forEach((workflow) => { + if (workflow.parent) { + const key = `${workflow.parent.workflowId}:${workflow.parent.runId}`; + const children = childrenMap.get(key) || []; + children.push(workflow); + childrenMap.set(key, children); + } + }); + + const buildNode = ( + workflow: WorkflowExecution, + paths: { runId: string; workflowId: string }[], + ): RootNode => { + const key = `${workflow.id}:${workflow.runId}`; + const children = childrenMap.get(key) || []; + + const node: RootNode = { + workflow, + scheduleId: + workflow?.searchAttributes?.indexedFields?.TemporalScheduledById, + children: [], + rootPaths: [...paths, { runId: workflow.runId, workflowId: workflow.id }], + }; + + node.children = children.map((child) => buildNode(child, node.rootPaths)); + + return node; + }; + + return buildNode(root, []); +}; + +const buildDirectRoots = ({ + parent, + workflow, + children, + siblingCount, +}: { + parent: WorkflowExecution | undefined; + workflow: WorkflowExecution; + children: WorkflowExecution[]; + siblingCount: number; +}): RootNode => { + const childNodes: RootNode[] = children.map((child) => { + const rootPaths = parent + ? [ + { runId: parent.runId, workflowId: parent.id }, + { runId: workflow.runId, workflowId: workflow.id }, + { runId: child.runId, workflowId: child.id }, + ] + : [ + { runId: workflow.runId, workflowId: workflow.id }, + { runId: child.runId, workflowId: child.id }, + ]; + return { + workflow: child, + siblingCount, + children: [], + rootPaths, + }; + }); + + const currentNode: RootNode = { + workflow, + children: childNodes, + rootPaths: parent + ? [ + { runId: parent.runId, workflowId: parent.id }, + { runId: workflow.runId, workflowId: workflow.id }, + ] + : [{ runId: workflow.runId, workflowId: workflow.id }], + }; + + if (!parent) return currentNode; + + const parentNode: RootNode = { + workflow: parent, + children: [currentNode], + rootPaths: [{ runId: parent.runId, workflowId: parent.id }], + }; + + return parentNode; +}; + +export async function fetchAllRootWorkflowsCount( + namespace: string, + rootWorkflowId: string, + rootRunId?: string, +): Promise { + let query = `RootWorkflowId = "${rootWorkflowId}"`; + if (rootRunId) { + query += ` AND RootRunId = "${rootRunId}"`; + } + + const count = await fetchWorkflowCountByExecutionStatus({ + namespace, + query, + }); + return count; +} + +export async function fetchAllRootWorkflows( + namespace: string, + rootWorkflowId: string, + rootRunId?: string, +): Promise { + let query = `RootWorkflowId = "${rootWorkflowId}"`; + if (rootRunId) { + query += ` AND RootRunId = "${rootRunId}"`; + } + + const root = await fetchWorkflow({ + namespace, + workflowId: rootWorkflowId, + runId: rootRunId, + }); + const workflows = await fetchAllPaginatedWorkflows(namespace, { query }); + return buildRoots(root?.workflow, workflows); +} + +type DirectWorkflowInputs = { + namespace: string; + parentWorkflowId: string; + parentRunId?: string; + workflow: WorkflowExecution; +}; + +export async function fetchAllDirectWorkflows({ + namespace, + parentWorkflowId, + parentRunId, + workflow, +}: DirectWorkflowInputs): Promise { + let parent; + + const fetchChildWorkflows = async ( + workflowId: string, + runId: string, + ): Promise => { + const query = `ParentWorkflowId = "${workflowId}" AND ParentRunId = "${runId}"`; + return await fetchAllPaginatedWorkflows(namespace, { query }); + }; + + let siblingCount = 0; + if (parentWorkflowId) { + parent = await fetchWorkflow({ + namespace, + workflowId: parentWorkflowId, + runId: parentRunId, + }); + + const query = `ParentWorkflowId = "${parentWorkflowId}" AND ParentRunId = "${parentRunId}"`; + const { count } = await fetchWorkflowCountByExecutionStatus({ + namespace, + query, + }); + siblingCount = parseInt(count); + } + + const children = await fetchChildWorkflows(workflow.id, workflow.runId); + return buildDirectRoots({ + parent: parent?.workflow, + workflow, + children, + siblingCount, + }); +} + +export const fetchAllPaginatedWorkflows = async ( + namespace: string, + parameters: ValidWorkflowParameters, + request = fetch, + archived = false, +): Promise => { + const rawQuery = + parameters.query || toListWorkflowQuery(parameters, archived); + let query: string; + try { + query = decodeURIComponent(rawQuery); + } catch { + query = rawQuery; + } + + const route = routeForApi('workflows', { namespace }); + const { executions } = await paginated(async (token: string) => { + return requestFromAPI(route, { + token, + request, + params: { query }, + }); + }); + return toWorkflowExecutions({ executions }); +}; + +type PaginatedWorkflowsPromise = ( + pageSize: number, + token: string, +) => Promise<{ items: WorkflowExecution[]; nextPageToken: string }>; + +export const fetchPaginatedWorkflows = async ( + namespace: string, + query: string = '', + request = fetch, +): Promise => { + return (pageSize = 100, token = '') => { + workflowError.set(''); + + const onError: ErrorCallback = (err) => { + handleUnauthorizedOrForbiddenError(err); + + if (get(hideWorkflowQueryErrors)) { + workflowError.set(translate('workflows.workflows-error-querying')); + } else { + workflowError.set( + err?.body?.message || translate('workflows.workflows-error-querying'), + ); + } + }; + + const route = routeForApi('workflows', { namespace }); + return requestFromAPI(route, { + params: { + pageSize: String(pageSize), + nextPageToken: token, + ...(query ? { query } : {}), + }, + request, + onError, + handleError: onError, + }).then(({ executions = [], nextPageToken = '' }) => { + return { + items: toWorkflowExecutions({ executions }), + nextPageToken: nextPageToken ? String(nextPageToken) : '', + }; + }); + }; +}; diff --git a/src/lib/stores/active-events.ts b/src/lib/stores/active-events.ts new file mode 100644 index 000000000..5b4f8bba9 --- /dev/null +++ b/src/lib/stores/active-events.ts @@ -0,0 +1,31 @@ +import { get, writable } from 'svelte/store'; + +import type { EventGroup } from '$lib/models/event-groups/event-groups'; + +export const indexPageSize = 200; +export const startIndex = writable(0); +export const endIndex = writable(indexPageSize); + +export const activeGroups = writable([]); +export const activeGroupHeight = writable(0); + +export const clearActives = () => { + activeGroups.set([]); + activeGroupHeight.set(0); + startIndex.set(0); + endIndex.set(indexPageSize); +}; + +export const clearActiveGroups = () => { + activeGroups.set([]); + activeGroupHeight.set(0); +}; + +export const setActiveGroup = (group: EventGroup) => { + if (!get(activeGroups).includes(group.id)) { + activeGroups.set([group.id]); + } else { + activeGroupHeight.set(0); + activeGroups.set([]); + } +}; diff --git a/src/lib/stores/advanced-visibility.ts b/src/lib/stores/advanced-visibility.ts new file mode 100644 index 000000000..419260cdc --- /dev/null +++ b/src/lib/stores/advanced-visibility.ts @@ -0,0 +1,28 @@ +import { derived } from 'svelte/store'; + +import { page } from '$app/stores'; + +import { + advancedVisibilityEnabled, + advancedVisibilityEnabledWithOrderBy, +} from '$lib/utilities/advanced-visibility-enabled'; + +import { cluster } from './cluster'; +import { temporalVersion } from './versions'; + +export const isCloud = derived( + [page], + ([$page]) => $page.data?.settings?.runtimeEnvironment?.isCloud, +); + +export const supportsAdvancedVisibility = derived( + [cluster, temporalVersion, isCloud], + ([$cluster, $temporalVersion, $isCloud]) => + advancedVisibilityEnabled($cluster, $temporalVersion) || $isCloud, +); + +export const supportsAdvancedVisibilityWithOrderBy = derived( + [cluster, isCloud], + ([$cluster, $isCloud]) => + advancedVisibilityEnabledWithOrderBy($cluster) || $isCloud, +); diff --git a/src/lib/stores/api-pagination.test.ts b/src/lib/stores/api-pagination.test.ts index b186e979c..a09c2247e 100644 --- a/src/lib/stores/api-pagination.test.ts +++ b/src/lib/stores/api-pagination.test.ts @@ -1,167 +1,568 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { describe, expect, it } from 'vitest'; import { get } from 'svelte/store'; -import { createPaginationStore } from './api-pagination'; -const items = new Array(50).fill(null).map((_, i) => i); +import { describe, expect, it } from 'vitest'; -describe('createPaginationStore', () => { - const store = createPaginationStore(); +import { createPaginationStore, getInitialPageSize } from './api-pagination'; +import { options } from './pagination'; - it('should have a default pageSize', () => { - const { pageSize } = get(store); +const items = new Array(50).fill(null).map((_, i) => i); +describe('createPaginationStore with default pageSizeOptions', () => { + it('should set correct getInitialPageSize with default options', () => { + const pageSize = getInitialPageSize(options); expect(pageSize).toBe(100); }); - it('should setUpdating to true', () => { - store.setUpdating(); + it('should set default values', () => { + const store = createPaginationStore(); - const { updating } = get(store); - expect(updating).toBe(true); + const { + key, + loading, + updating, + index, + hasNextIndexData, + indexStart, + indexEnd, + activeIndex, + indexData, + pageSize, + previousPageSize, + hasNext, + hasPrevious, + } = get(store); + + expect(key).toBe('per-page'); + expect(loading).toBe(true); + expect(updating).toBe(false); + expect(index).toBe(0); + expect(hasNextIndexData).toBe(false); + expect(indexStart).toBe(0); + expect(indexEnd).toBe(0); + expect(indexData).toStrictEqual({}); + expect(pageSize).toBe(100); + expect(previousPageSize).toBe(100); + expect(activeIndex).toBe(0); + expect(hasPrevious).toBe(false); + expect(hasNext).toBe(false); }); - it('should set the endingPageNumber to zero if there are no items', () => { - const { endingPageNumber } = get(store); + it('should update store correctly on first nextPageWithItems() with empty items', () => { + const store = createPaginationStore(); + + store.nextPageWithItems('', []); - expect(endingPageNumber).toBe(0); + const { + index, + hasNextIndexData, + indexStart, + indexEnd, + hasPrevious, + indexData, + } = get(store); + expect(index).toBe(0); + expect(hasNextIndexData).toBe(false); + expect(indexStart).toBe(0); + expect(indexEnd).toBe(0); + expect(hasPrevious).toBe(false); + expect(indexData).toStrictEqual({}); }); - it('should update indexTokens and set hasPrevious to false for the first page', () => { - store.setNextPageToken('token1', items); + it('should update store correctly on first nextPageWithItems()', () => { + const store = createPaginationStore(); - const { index, indexTokens, hasPrevious } = get(store); + store.nextPageWithItems('token', items); + + const { + index, + hasNextIndexData, + indexStart, + indexEnd, + hasPrevious, + indexData, + } = get(store); expect(index).toBe(0); - expect(indexTokens).toEqual({ - '1': 'token1', - }); + expect(hasNextIndexData).toBe(false); + expect(indexStart).toBe(1); + expect(indexEnd).toBe(items.length); expect(hasPrevious).toBe(false); + expect(indexData).toStrictEqual({ + 0: { nextToken: 'token', start: 1, end: items.length, items }, + }); }); - it('should increment the endingPageNumber based on the number of items', () => { - const { endingPageNumber } = get(store); + it('should update store correctly on multiple nextPageWithItems()', () => { + const store = createPaginationStore(); + + store.nextPageWithItems('token1', items); + store.nextPageWithItems('token2', items); - expect(endingPageNumber).toBe(50); + const { + index, + hasNextIndexData, + indexStart, + indexEnd, + hasPrevious, + indexData, + } = get(store); + expect(index).toBe(1); + expect(hasNextIndexData).toBe(false); + expect(indexStart).toBe(items.length + 1); + expect(indexEnd).toBe(items.length + items.length); + expect(hasPrevious).toBe(true); + expect(indexData[index - 1]).toStrictEqual({ + nextToken: 'token1', + start: 1, + end: items.length, + items, + }); + + expect(indexData[index]).toStrictEqual({ + nextToken: 'token2', + start: items.length + 1, + end: items.length * 2, + items, + }); }); - it('should update the nextIndex when going to nextPage', () => { - const { nextIndex: initialNextIndex } = get(store); - expect(initialNextIndex).toBe(0); + it('should update store correctly on multiple nextPageWithItems() and an empty item', () => { + const store = createPaginationStore(); - store.nextPage(); + store.nextPageWithItems('token1', items); + store.nextPageWithItems('token2', items); + store.nextPageWithItems('', []); - const { nextIndex } = get(store); - expect(nextIndex).toBe(1); + const { + index, + hasNextIndexData, + indexStart, + indexEnd, + hasPrevious, + indexData, + } = get(store); + expect(index).toBe(1); + expect(hasNextIndexData).toBe(false); + expect(indexStart).toBe(items.length + 1); + expect(indexEnd).toBe(items.length + items.length); + expect(hasPrevious).toBe(true); + expect(indexData[index - 1]).toStrictEqual({ + nextToken: 'token1', + start: 1, + end: items.length, + items, + }); + + expect(indexData[index]).toStrictEqual({ + nextToken: 'token2', + start: items.length + 1, + end: items.length * 2, + items, + }); }); - it('should update indexTokens and set hasPrevious to true for the next page', () => { - store.setNextPageToken('token2', items); + it('should update store correctly on nextPage()', () => { + const store = createPaginationStore(); - const { index, indexTokens, hasPrevious } = get(store); - expect(indexTokens).toEqual({ - '1': 'token1', - '2': 'token2', - }); + store.nextPage(); + + const { index, hasPrevious, hasNext, indexData } = get(store); expect(index).toBe(1); expect(hasPrevious).toBe(true); + expect(hasNext).toBe(false); + expect(indexData).toStrictEqual({}); }); - it('should increment the endingPageNumber based on the page', () => { - const { endingPageNumber } = get(store); + it('should update store correctly on multiple nextPage()', () => { + const store = createPaginationStore(); - expect(endingPageNumber).toBe(150); + store.nextPage(); + store.nextPage(); + + const { index, hasPrevious, hasNext, indexData } = get(store); + expect(index).toBe(2); + expect(hasPrevious).toBe(true); + expect(hasNext).toBe(false); + expect(indexData).toStrictEqual({}); }); - it('should update the nextIndex when going to previousPage', () => { - const { nextIndex: initialNextIndex } = get(store); - expect(initialNextIndex).toBe(1); + it('should update store correctly on previousPage()', () => { + const store = createPaginationStore(); + store.nextPage(); store.previousPage(); - const { nextIndex } = get(store); - expect(nextIndex).toBe(0); + const { index, hasPrevious, indexData } = get(store); + expect(index).toBe(0); + expect(hasPrevious).toBe(false); + expect(indexData).toStrictEqual({}); }); - it('should set hasPrevious to false if the previous page is the first page', () => { - const { nextIndex } = get(store); - expect(nextIndex).toBe(0); + it('should update store correctly on previousPage() when at 0 index', () => { + const store = createPaginationStore(); - store.setNextPageToken('token1', items); + store.nextPage(); + store.previousPage(); + store.previousPage(); - const { index, hasPrevious } = get(store); + const { index, hasPrevious, indexData } = get(store); expect(index).toBe(0); expect(hasPrevious).toBe(false); + expect(indexData).toStrictEqual({}); }); - it('should decrement the endingPageNumber based on the page', () => { - const { endingPageNumber } = get(store); + it('should update store correctly on multiple nextPageWithItems() and previousPage', () => { + const store = createPaginationStore(); + + store.nextPageWithItems('token1', items); + store.nextPageWithItems('token2', items); + store.nextPageWithItems('token3', items); + store.previousPage(); - expect(endingPageNumber).toBe(50); + const { + index, + hasNextIndexData, + indexStart, + indexEnd, + hasPrevious, + hasNext, + indexData, + } = get(store); + expect(index).toBe(1); + expect(hasNextIndexData).toBe(true); + expect(indexStart).toBe(items.length + 1); + expect(indexEnd).toBe(items.length * 2); + expect(hasPrevious).toBe(true); + expect(hasNext).toBe(true); + expect(indexData[index - 1]).toStrictEqual({ + nextToken: 'token1', + start: 1, + end: items.length, + items, + }); + expect(indexData[index]).toStrictEqual({ + nextToken: 'token2', + start: items.length + 1, + end: items.length * 2, + items, + }); + expect(indexData[index + 1]).toStrictEqual({ + nextToken: 'token3', + start: items.length * 2 + 1, + end: items.length * 3, + items, + }); }); - it('should reset index and nextIndex and set updating to true with resetPageSize', () => { + it('should update store correctly on multiple nextPageWithItems() and no nextPageToken and previousPage() / nextPage() to last page', () => { + const store = createPaginationStore(); + + store.nextPageWithItems('token1', items); + store.nextPageWithItems('token2', items); + store.nextPageWithItems('', items); + store.previousPage(); + store.previousPage(); + store.nextPage(); store.nextPage(); - store.setNextPageToken('token2', items); const { - index: initialIndex, - nextIndex: initialNextIndex, - updating: initialUpdating, + index, + hasNextIndexData, + indexStart, + indexEnd, + hasPrevious, + hasNext, + indexData, } = get(store); + expect(index).toBe(2); + expect(hasNextIndexData).toBe(false); + expect(indexStart).toBe(items.length * 2 + 1); + expect(indexEnd).toBe(items.length * 3); + expect(hasPrevious).toBe(true); + expect(hasNext).toBe(false); + + expect(indexData[index - 2]).toStrictEqual({ + nextToken: 'token1', + start: 1, + end: items.length, + items, + }); + expect(indexData[index - 1]).toStrictEqual({ + nextToken: 'token2', + start: items.length + 1, + end: items.length * 2, + items, + }); + expect(indexData[index]).toStrictEqual({ + nextToken: '', + start: items.length * 2 + 1, + end: items.length * 3, + items, + }); + }); - expect(initialIndex).toBe(1); - expect(initialNextIndex).toBe(1); - expect(initialUpdating).toBe(false); + it('should update store correctly on setNextRow() with no items', () => { + const store = createPaginationStore(); - store.resetPageSize(); + store.nextRow(); - const { index, nextIndex, updating } = get(store); + const { activeIndex } = get(store); + expect(activeIndex).toBe(0); + }); - expect(index).toBe(0); - expect(nextIndex).toBe(0); + it('should update store correctly on multiple setNextRow()', () => { + const store = createPaginationStore(); + + store.nextPageWithItems('', items); + + store.nextRow(); + store.nextRow(); + store.nextRow(); + store.nextRow(); + + const { activeIndex } = get(store); + expect(activeIndex).toBe(4); + }); + + it('should update store correctly on multiple setNextRow() when reaching the end of the items length', () => { + const store = createPaginationStore(); + + store.nextPageWithItems('', ['a', 'b', 'c']); + + store.nextRow(); + store.nextRow(); + store.nextRow(); + store.nextRow(); + + const { activeIndex } = get(store); + expect(activeIndex).toBe(2); + }); + + it('should update store correctly on setPreviousRow() with no items', () => { + const store = createPaginationStore(); + + store.previousRow(); + + const { activeIndex } = get(store); + expect(activeIndex).toBe(0); + }); + + it('should update store correctly on multiple setPreviousRow()', () => { + const store = createPaginationStore(); + + store.nextPageWithItems('', items); + + store.nextRow(); + store.nextRow(); + store.nextRow(); + store.nextRow(); + store.previousRow(); + store.previousRow(); + + const { activeIndex } = get(store); + expect(activeIndex).toBe(2); + }); + + it('should update store correctly on multiple setNextPrevious() when reaching the start of the items length', () => { + const store = createPaginationStore(); + + store.nextPageWithItems('', ['a', 'b', 'c']); + + store.nextRow(); + store.nextRow(); + store.previousRow(); + store.previousRow(); + store.previousRow(); + store.previousRow(); + + const { activeIndex } = get(store); + expect(activeIndex).toBe(0); + }); + + it('should update store correctly on setUpdating()', () => { + const store = createPaginationStore(); + + store.setUpdating(); + + const { updating } = get(store); expect(updating).toBe(true); }); - it('should reset to the initial store', () => { + it('should update store correctly on setUpdating() and getNextPageItems()', () => { + const store = createPaginationStore(); + + store.setUpdating(); + store.nextPageWithItems('', items); + + const { updating } = get(store); + expect(updating).toBe(false); + }); + + it('should update store correctly on setActiveIndex()', () => { + const store = createPaginationStore(); + + store.nextPageWithItems('', items); + + store.nextRow(); + store.nextRow(); + store.setActiveIndex(35); + + const { activeIndex } = get(store); + expect(activeIndex).toBe(35); + }); + + it('should update store correctly on reset()', () => { + const store = createPaginationStore(); + + store.nextPageWithItems('token1', items); + store.nextPageWithItems('token2', items); + store.nextPageWithItems('token3', items); + + store.nextRow(); + store.nextRow(); + store.nextRow(); + store.nextRow(); + store.reset(); const { - index, - nextIndex, - pageSize, - currentPageNumber, - endingPageNumber, - items, + key, loading, updating, - indexTokens, + index, + hasNextIndexData, + indexStart, + indexEnd, + activeIndex, + indexData, + pageSize, + previousPageSize, hasNext, hasPrevious, } = get(store); + expect(key).toBe('per-page'); + expect(loading).toBe(true); + expect(updating).toBe(false); expect(index).toBe(0); - expect(nextIndex).toBe(0); + expect(hasNextIndexData).toBe(false); + expect(indexStart).toBe(0); + expect(indexEnd).toBe(0); + expect(indexData).toStrictEqual({}); expect(pageSize).toBe(100); - expect(currentPageNumber).toBe(1); - expect(endingPageNumber).toBe(0); - expect(items).toEqual([]); - expect(loading).toBe(true); - expect(updating).toBe(true); - expect(indexTokens).toEqual({}); - expect(hasNext).toBe(true); + expect(previousPageSize).toBe(100); + expect(activeIndex).toBe(0); expect(hasPrevious).toBe(false); + expect(hasNext).toBe(false); + }); +}); + +describe('createPaginationStore with custom pageSizeOptions', () => { + it('should set correct getInitialPageSize', () => { + const options = ['10', '20', '50']; + const pageSize = getInitialPageSize(options); + expect(pageSize).toBe(10); }); - it('should set hasNext to false and not update items if next page has no items', () => { - store.setNextPageToken('token1', items); + it('should set fallback to default pageSize with no options', () => { + const options = []; + const pageSize = getInitialPageSize(options); + expect(pageSize).toBe(100); + }); + + it('should set fallback to default pageSize with no options', () => { + const options = ['a', 'b', 'c']; + const pageSize = getInitialPageSize(options); + expect(pageSize).toBe(100); + }); + + it('should set correct getInitialPageSize with defaultPageSize', () => { + const options = [10, 25, 50]; + const defaultPageSize = 25; + const pageSize = getInitialPageSize(options, defaultPageSize); + expect(pageSize).toBe(25); + }); + + it('should set correct getInitialPageSize with defaultPageSize that does not match options', () => { + const options = [10, 25, 50]; + const defaultPageSize = 20; + const pageSize = getInitialPageSize(options, defaultPageSize); + expect(pageSize).toBe(20); + }); + + it('should set fallback getInitialPageSize with bad defaultPageSize', () => { + const options = [10, 25, 50]; + const defaultPageSize = 'cats'; + const pageSize = getInitialPageSize(options, defaultPageSize); + expect(pageSize).toBe(10); + }); + + it('should set default values', () => { + const store = createPaginationStore(['10', '20', '50']); + + const { + key, + loading, + updating, + index, + hasNextIndexData, + indexStart, + indexEnd, + indexData, + pageSize, + previousPageSize, + activeIndex, + hasNext, + hasPrevious, + } = get(store); - const { hasNext: initialHasNext } = get(store); - expect(initialHasNext).toBe(true); + expect(key).toBe('per-page'); + expect(loading).toBe(true); + expect(updating).toBe(false); + expect(index).toBe(0); + expect(hasNextIndexData).toBe(false); + expect(indexStart).toBe(0); + expect(indexEnd).toBe(0); + expect(indexData).toStrictEqual({}); + expect(pageSize).toBe(10); + expect(previousPageSize).toBe(10); + expect(activeIndex).toBe(0); + expect(hasPrevious).toBe(false); + expect(hasNext).toBe(false); + }); - store.setNextPageToken('', []); + it('should resetPageSize', () => { + const store = createPaginationStore(['10', '20', '50']); + store.resetPageSize(50); - const { hasNext, items: storeItems } = get(store); + const { + key, + loading, + updating, + index, + hasNextIndexData, + indexStart, + indexEnd, + indexData, + pageSize, + previousPageSize, + activeIndex, + hasNext, + hasPrevious, + } = get(store); + + expect(key).toBe('per-page'); + expect(loading).toBe(true); + expect(updating).toBe(false); + expect(index).toBe(0); + expect(hasNextIndexData).toBe(false); + expect(indexStart).toBe(0); + expect(indexEnd).toBe(0); + expect(indexData).toStrictEqual({}); + expect(pageSize).toBe(50); + expect(previousPageSize).toBe(50); + expect(activeIndex).toBe(0); + expect(hasPrevious).toBe(false); expect(hasNext).toBe(false); - expect(storeItems).toEqual(items); }); }); diff --git a/src/lib/stores/api-pagination.ts b/src/lib/stores/api-pagination.ts index 6c5173c03..ff3a6c035 100644 --- a/src/lib/stores/api-pagination.ts +++ b/src/lib/stores/api-pagination.ts @@ -1,140 +1,249 @@ +import type { Readable } from 'svelte/store'; import { derived, writable } from 'svelte/store'; + import { page } from '$app/stores'; -import { perPageFromSearchParameter, defaultItemsPerPage } from './pagination'; -import type { Readable } from 'svelte/store'; +import { + defaultItemsPerPage, + options, + perPageFromSearchParameter, + perPageKey, +} from './pagination'; -type PaginationMethods = { +type PaginationMethods = { + nextPageWithItems: (t: string, items: T[]) => void; nextPage: () => void; previousPage: () => void; - setNextPageToken: (t: string, items: any[]) => void; setUpdating: () => void; reset: () => void; - resetPageSize: () => void; + resetPageSize: (pageSize: number) => void; + nextRow: () => void; + previousRow: () => void; + setActiveIndex: (activeIndex: number) => void; }; -type PaginationItems = { +type PaginationItems = { key: string; - index: number; - nextIndex: number; - pageSize: number; - currentPageNumber: number; - endingPageNumber: number; - items: any[]; loading: boolean; updating: boolean; - indexTokens: Record; hasNext: boolean; hasPrevious: boolean; + index: number; + previousPageSize: number; + pageSize: number; + indexData: Record< + number, + { nextToken: string; start: number; end: number; items: T[] } + >; + visibleItems: T[]; + hasNextIndexData: boolean; + indexStart: number; + indexEnd: number; + activeIndex: number; }; -const initialStore: PaginationItems = { - key: 'per-page', - index: 0, - nextIndex: 0, - pageSize: defaultItemsPerPage, - currentPageNumber: 1, - endingPageNumber: defaultItemsPerPage, - items: [], +const defaultStore = { + key: perPageKey, loading: true, - updating: true, - indexTokens: {}, - hasNext: true, + updating: false, + hasNext: false, hasPrevious: false, + index: 0, + previousPageSize: defaultItemsPerPage, + pageSize: defaultItemsPerPage, + indexData: {}, + visibleItems: [], + hasNextIndexData: false, + indexStart: 0, + indexEnd: 0, + activeIndex: 0, }; -export type PaginationStore = PaginationMethods & Readable; +export type PaginationStore = PaginationMethods & + Readable>; -export function createPaginationStore(): PaginationStore { - const paginationStore = writable(initialStore); +const setFirstOption = (options: string[] | number[]) => { + const defaultOption = options[0]; + if (!defaultOption) return defaultItemsPerPage; + const optionAsInt = parseInt(defaultOption.toString()); + if (isNaN(optionAsInt)) return defaultItemsPerPage; + return optionAsInt; +}; + +export const getInitialPageSize = ( + options: string[] | number[], + defaultPageSize: string | number | undefined = undefined, +) => { + if (defaultPageSize) { + const optionAsInt = parseInt(defaultPageSize.toString()); + if (isNaN(optionAsInt)) return setFirstOption(options); + return optionAsInt; + } else { + return setFirstOption(options); + } +}; + +export function createPaginationStore( + pageSizeOptions: string[] | number[] = options, + defaultPageSize: string | number | undefined = undefined, +): PaginationStore { + const initialPageSize = getInitialPageSize(pageSizeOptions, defaultPageSize); + const paginationStore = writable({ + ...defaultStore, + previousPageSize: initialPageSize, + pageSize: initialPageSize, + }); const { set, update } = paginationStore; const pageSize = derived([page], ([$page]) => { - return perPageFromSearchParameter( - $page.url.searchParams.get('per-page') ?? undefined, - ); + const perPage = $page.url.searchParams.get(perPageKey); + return perPage ? perPageFromSearchParameter(perPage) : undefined; }); const { subscribe } = derived( [paginationStore, pageSize], ([$pagination, $pageSize]) => { - const getEndingPageNumber = () => { - if ($pagination.items.length < $pageSize && $pagination.index === 0) { - return $pagination.items.length; - } - - if ($pagination.items.length < $pageSize) { - return $pagination.index * $pageSize + $pagination.items.length; - } - - return $pagination.index * $pageSize + $pageSize; - }; + const visibleItems = + $pagination.indexData[$pagination.index]?.items ?? []; + const indexStart = $pagination.indexData[$pagination.index]?.start ?? 0; + const indexEnd = $pagination.indexData[$pagination.index]?.end ?? 0; + const hasNextIndexData = Boolean( + $pagination.indexData[$pagination.index + 1], + ); return { ...$pagination, - currentPageNumber: $pagination.index * $pageSize + 1, - endingPageNumber: getEndingPageNumber(), - pageSize: $pageSize, + visibleItems, + indexStart, + indexEnd, + hasNextIndexData, + pageSize: $pageSize ?? $pagination.pageSize, }; }, ); - const setNextPageToken = ( + const setNextPageWithItems = ( nextToken: string, - items: any[], - store: PaginationItems, + items: T[], + _store: PaginationItems, ) => { - const _store = { ...store }; - _store.hasNext = Boolean(nextToken); - _store.updating = false; - _store.loading = false; - - // Return early if page does not have any items (this can happen when the page size equals the number of items) - if (!items.length) return _store; - - _store.items = items; - _store.index = store.nextIndex; - _store.indexTokens = { ...store.indexTokens }; - - if (store.nextIndex === store.index) { - // First page - _store.indexTokens[store.nextIndex + 1] = nextToken; - _store.hasPrevious = false; - } else if (store.nextIndex > store.index) { - // Next Page - _store.indexTokens[store.nextIndex + 1] = nextToken; - _store.hasPrevious = true; - } else if (store.nextIndex < store.index) { - // Previous Page - if (store.nextIndex === 0) _store.hasPrevious = false; + const currentIndex = _store.index; + const store = { + ..._store, + indexData: { ..._store.indexData }, + hasNext: Boolean(nextToken), + updating: false, + loading: false, + }; + + if (!items.length) return { ...store, hasNext: false }; + + if (!store.indexData[currentIndex]) { + store.indexData[currentIndex] = { + nextToken, + start: 1, + end: items.length, + items, + }; + store.hasPrevious = false; + } else { + (store.index = currentIndex + 1), + (store.indexData[store.index] = { + nextToken, + start: store.indexData[currentIndex].end + 1, + end: store.indexData[currentIndex].end + items.length, + items, + }); + store.hasPrevious = true; + } + + return store; + }; + + const setNextPage = (_store: PaginationItems) => { + const store = { + ..._store, + index: _store.index + 1, + hasPrevious: true, + loading: false, + updating: false, + }; + + if (!store.indexData[store.index]?.nextToken) { + store.hasNext = false; } - return _store; + return store; + }; + + const setPreviousPage = (_store: PaginationItems) => { + const store = { + ..._store, + hasNext: true, + updating: false, + loading: false, + index: _store.index > 0 ? _store.index - 1 : 0, + }; + + if (store.index === 0) { + store.hasPrevious = false; + } + + return store; + }; + + const setNextRow = (_store: PaginationItems) => { + const store = { ..._store }; + const indexLength = store.indexData[store.index]?.items?.length ?? 0; + + if (store.activeIndex < indexLength - 1) { + store.activeIndex = store.activeIndex + 1; + } + + return store; + }; + + const setPreviousRow = (_store: PaginationItems) => { + const store = { ..._store }; + const activeIndex = store.activeIndex >= 1 ? store.activeIndex - 1 : 0; + + store.activeIndex = activeIndex; + + return store; + }; + + const setActiveIndex = ( + _store: PaginationItems, + activeIndex: number, + ) => { + return { ..._store, activeIndex }; + }; + + const resetPageSize = (_store: PaginationItems, pageSize: number) => { + return { + ..._store, + pageSize, + previousPageSize: pageSize, + index: 0, + indexData: {}, + loading: true, + updating: false, + }; }; return { subscribe, - nextPage: () => - update((store) => { - return { ...store, nextIndex: store.index + 1 }; - }), - previousPage: () => - update((store) => { - if (store.index > 0) { - return { ...store, nextIndex: store.index - 1 }; - } - return store; - }), - setNextPageToken: (token: string, items: any[]) => - update((store) => setNextPageToken(token, items, store)), - setUpdating: () => - update((store) => { - return { ...store, updating: true }; - }), - reset: () => set(initialStore), - resetPageSize: () => - update((store) => { - return { ...store, index: 0, nextIndex: 0, updating: true }; - }), + nextPageWithItems: (token: string, items: T[]) => + update((store) => setNextPageWithItems(token, items, store)), + nextPage: () => update((store) => setNextPage(store)), + previousPage: () => update((store) => setPreviousPage(store)), + setUpdating: () => update((store) => ({ ...store, updating: true })), + reset: () => set(defaultStore), + resetPageSize: (pageSize) => + update((store) => resetPageSize(store, pageSize)), + nextRow: () => update((store) => setNextRow(store)), + previousRow: () => update((store) => setPreviousRow(store)), + setActiveIndex: (activeIndex: number) => + update((store) => setActiveIndex(store, activeIndex)), }; } diff --git a/src/lib/stores/auth-user.ts b/src/lib/stores/auth-user.ts index 73bc3491a..4987f9828 100644 --- a/src/lib/stores/auth-user.ts +++ b/src/lib/stores/auth-user.ts @@ -1,5 +1,7 @@ import { get } from 'svelte/store'; + import { persistStore } from '$lib/stores/persist-store'; +import type { User } from '$lib/types/global'; export const authUser = persistStore('AuthUser', {}); diff --git a/src/lib/stores/banner.ts b/src/lib/stores/banner.ts deleted file mode 100644 index c4b0f83e2..000000000 --- a/src/lib/stores/banner.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { get } from 'svelte/store'; -import { persistStore } from '$lib/stores/persist-store'; - -export const closedBanners = persistStore('closedBanners', null); - -export const close = (bannerId: string): void => { - let banners: string[] = get(closedBanners) ?? []; - banners = [...banners, bannerId]; - banners = banners.slice(banners.length - 10, banners.length); - - closedBanners.set(banners); -}; diff --git a/src/lib/stores/batch-operations.ts b/src/lib/stores/batch-operations.ts new file mode 100644 index 000000000..9f52d825f --- /dev/null +++ b/src/lib/stores/batch-operations.ts @@ -0,0 +1,22 @@ +import { writable } from 'svelte/store'; + +import { pollBatchOperation } from '$lib/services/batch-service'; +import { persistStore } from '$lib/stores/persist-store'; + +export const inProgressBatchOperation = writable<{ + namespace: string; + jobId: string; +}>(); + +inProgressBatchOperation.subscribe(async (operation) => { + if (operation) { + await pollBatchOperation(operation).then(() => + inProgressBatchOperation.set(undefined), + ); + } +}); + +export const autoRefresh = persistStore( + 'auto-refresh-batch-operation', + false, +); diff --git a/src/lib/stores/bulk-actions.test.ts b/src/lib/stores/bulk-actions.test.ts new file mode 100644 index 000000000..aa4a8f2f1 --- /dev/null +++ b/src/lib/stores/bulk-actions.test.ts @@ -0,0 +1,106 @@ +import { get, type writable as writableFunc } from 'svelte/store'; + +import { describe, expect, test, vi } from 'vitest'; + +import { supportsBulkActions } from './bulk-actions'; + +const mockedPageStore = await vi.hoisted(async () => { + const { writable } = await vi.importActual<{ + writable: typeof writableFunc; + }>('svelte/store'); + + const writableStore = writable(); + return { + subscribe: writableStore.subscribe, + mockSetSubscribeValue: (value: unknown): void => writableStore.set(value), + }; +}); + +vi.mock('$app/stores', () => ({ + page: mockedPageStore, +})); + +describe('supportsBulkActions store', () => { + describe('for Cloud', () => { + test('returns true when batch actions are enabled, and visibility store is elasticsearch regardless of server version', () => { + mockedPageStore.mockSetSubscribeValue({ + data: { + settings: { + runtimeEnvironment: { isCloud: true }, + batchActionsDisabled: false, + }, + cluster: { serverVersion: '1.0.0', visibilityStore: 'elasticsearch' }, + }, + }); + + expect(get(supportsBulkActions)).toBe(true); + }); + }); + + describe('for Local', () => { + test('returns true when version is newer than 1.18.0, advanced visibility is supported, and batch actions are enabled', () => { + mockedPageStore.mockSetSubscribeValue({ + data: { + settings: { + runtimeEnvironment: { isCloud: false }, + batchActionsDisabled: false, + }, + cluster: { + serverVersion: '1.19.0', + visibilityStore: 'elasticsearch', + }, + }, + }); + + expect(get(supportsBulkActions)).toBe(true); + }); + + test('returns false when version is older, even if visibility store is elasticsearch and batch actions are enabled', () => { + mockedPageStore.mockSetSubscribeValue({ + data: { + settings: { + runtimeEnvironment: { isCloud: false }, + batchActionsDisabled: false, + }, + cluster: { + serverVersion: '1.17.0', + visibilityStore: 'elasticsearch', + }, + }, + }); + + expect(get(supportsBulkActions)).toBe(false); + }); + + test('returns false when advanced visibility store is not elasticsearch, even if version is newer and batch actions are enabled', () => { + mockedPageStore.mockSetSubscribeValue({ + data: { + settings: { + runtimeEnvironment: { isCloud: false }, + batchActionsDisabled: false, + }, + cluster: { serverVersion: '1.19.0', visibilityStore: 'mysql' }, + }, + }); + + expect(get(supportsBulkActions)).toBe(false); + }); + + test('returns false when batch actions are not enabled, even if version is newer and advanced visibility is supported', () => { + mockedPageStore.mockSetSubscribeValue({ + data: { + settings: { + runtimeEnvironment: { isCloud: false }, + batchActionsDisabled: true, + }, + cluster: { + serverVersion: '1.19.0', + visibilityStore: 'elasticsearch', + }, + }, + }); + + expect(get(supportsBulkActions)).toBe(false); + }); + }); +}); diff --git a/src/lib/stores/bulk-actions.ts b/src/lib/stores/bulk-actions.ts index 0c74d3110..d536f18a4 100644 --- a/src/lib/stores/bulk-actions.ts +++ b/src/lib/stores/bulk-actions.ts @@ -1,14 +1,16 @@ import { derived } from 'svelte/store'; -import { advancedVisibilityEnabled } from '$lib/utilities/advanced-visibility-enabled'; +import { bulkActionsEnabled } from '$lib/utilities/bulk-actions-enabled'; import { isVersionNewer } from '$lib/utilities/version-check'; -import { cluster } from './cluster'; +import { isCloud, supportsAdvancedVisibility } from './advanced-visibility'; +import { settings } from './settings'; import { temporalVersion } from './versions'; export const supportsBulkActions = derived( - [temporalVersion, cluster], - ([$temporalVersion, $cluster]) => - isVersionNewer($temporalVersion, '1.18.0') && - advancedVisibilityEnabled($cluster), + [temporalVersion, supportsAdvancedVisibility, settings, isCloud], + ([$temporalVersion, $supportsAdvancedVisibility, $settings, $isCloud]) => + ($isCloud ? true : isVersionNewer($temporalVersion, '1.18.0')) && + $supportsAdvancedVisibility && + bulkActionsEnabled($settings), ); diff --git a/src/lib/stores/capability-enablement.ts b/src/lib/stores/capability-enablement.ts new file mode 100644 index 000000000..d4e23bf2b --- /dev/null +++ b/src/lib/stores/capability-enablement.ts @@ -0,0 +1,21 @@ +import { derived } from 'svelte/store'; + +import { page } from '$app/stores'; + +import { minimumVersionRequired } from '$lib/utilities/version-check'; + +import { temporalVersion } from './versions'; + +export const prefixSearchEnabled = derived( + [page, temporalVersion], + ([$page, $temporalVersion]) => { + const serverVersionEnabled = minimumVersionRequired( + '1.23.0', + $temporalVersion, + ); + const capabilitiesEnabled = Boolean( + $page.data?.systemInfo?.capabilities?.prefixSearch, + ); + return serverVersionEnabled || capabilitiesEnabled; + }, +); diff --git a/src/lib/stores/cluster.ts b/src/lib/stores/cluster.ts index ccb89f7fa..297683249 100644 --- a/src/lib/stores/cluster.ts +++ b/src/lib/stores/cluster.ts @@ -1,4 +1,7 @@ -import { writable } from 'svelte/store'; -import type { GetClusterInfoResponse } from '$types'; +import { derived } from 'svelte/store'; -export const cluster = writable({}); +import { page } from '$app/stores'; + +export const cluster = derived([page], ([$page]) => { + return $page.data?.cluster; +}); diff --git a/src/lib/stores/configurable-table-columns.test.ts b/src/lib/stores/configurable-table-columns.test.ts new file mode 100644 index 000000000..ab03595b3 --- /dev/null +++ b/src/lib/stores/configurable-table-columns.test.ts @@ -0,0 +1,55 @@ +import { get } from 'svelte/store'; + +import { describe, expect, test } from 'vitest'; + +import { + addColumn, + moveColumn, + persistedWorkflowTableColumns, + removeColumn, + TABLE_TYPE, +} from './configurable-table-columns'; + +describe('Workflow Table Columns store', () => { + describe('addColumn', () => { + test('moves a column from the availableColumns array to the columns array', () => { + persistedWorkflowTableColumns.set({ default: [] }); + addColumn('Workflow ID', 'default', TABLE_TYPE.WORKFLOWS); + expect(get(persistedWorkflowTableColumns)).toEqual({ + default: [{ label: 'Workflow ID' }], + }); + }); + }); + + describe('removeColumn', () => { + test('moves a column from the columns array to the availableColumns array and unpins it', () => { + persistedWorkflowTableColumns.set({ + default: [{ label: 'Workflow ID' }], + }); + removeColumn('Workflow ID', 'default', TABLE_TYPE.WORKFLOWS); + expect(get(persistedWorkflowTableColumns)).toEqual({ default: [] }); + }); + }); + + describe('moveColumn', () => { + test('moves a column up', () => { + persistedWorkflowTableColumns.set({ + default: [{ label: 'End' }, { label: 'Start' }], + }); + moveColumn(1, 0, 'default', TABLE_TYPE.WORKFLOWS); + expect(get(persistedWorkflowTableColumns)).toEqual({ + default: [{ label: 'Start' }, { label: 'End' }], + }); + }); + + test('moves a column down', () => { + persistedWorkflowTableColumns.set({ + default: [{ label: 'End' }, { label: 'Start' }], + }); + moveColumn(0, 1, 'default', TABLE_TYPE.WORKFLOWS); + expect(get(persistedWorkflowTableColumns)).toEqual({ + default: [{ label: 'Start' }, { label: 'End' }], + }); + }); + }); +}); diff --git a/src/lib/stores/configurable-table-columns.ts b/src/lib/stores/configurable-table-columns.ts new file mode 100644 index 000000000..653eac9f9 --- /dev/null +++ b/src/lib/stores/configurable-table-columns.ts @@ -0,0 +1,342 @@ +import { derived, type Readable, type Writable } from 'svelte/store'; + +import { page } from '$app/stores'; + +import type { Settings } from '$lib/types/global'; + +import { namespaces } from './namespaces'; +import { persistStore } from './persist-store'; +import { customSearchAttributes } from './search-attributes'; + +export const WorkflowHeaderLabels = [ + 'Status', + 'Workflow ID', + 'Run ID', + 'Type', + 'Start', + 'End', + 'History Size', + 'History Length', + 'Execution Time', + 'Execution Duration', + 'State Transitions', + 'Parent Namespace', + 'Task Queue', + 'Scheduled By ID', + 'Scheduled Start Time', +] as const; + +export type WorkflowHeaderLabel = (typeof WorkflowHeaderLabels)[number]; + +// https://github.com/microsoft/TypeScript/issues/29729 +// https://stackoverflow.com/a/61048124 +// eslint-disable-next-line @typescript-eslint/ban-types +type AnyWorkflowHeaderLabel = WorkflowHeaderLabel | (string & {}); + +export type ConfigurableTableHeader = { + label: AnyWorkflowHeaderLabel; + pinned?: boolean; +}; + +export const TABLE_TYPE = { + WORKFLOWS: 'workflows', + SCHEDULES: 'schedules', +} as const; + +type Keys = keyof typeof TABLE_TYPE; +export type ConfigurableTableType = (typeof TABLE_TYPE)[Keys]; + +type TableColumns = { + [namespace: string]: + | { + [key in ConfigurableTableType]: ConfigurableTableHeader[]; + } + | undefined; +}; + +type State = { + [namespace: string]: ConfigurableTableHeader[] | undefined; +}; + +type Action = + | { + type: 'CONFIGURABLE_COLUMN.ADD'; + payload: { + label: AnyWorkflowHeaderLabel; + namespace: string; + table: ConfigurableTableType; + }; + } + | { + type: 'CONFIGURABLE_COLUMN.REMOVE'; + payload: { + label: AnyWorkflowHeaderLabel; + namespace: string; + table: ConfigurableTableType; + }; + } + | { + type: 'CONFIGURABLE_COLUMN.MOVE'; + payload: { + from: number; + to: number; + namespace: string; + table: ConfigurableTableType; + }; + }; + +const DEFAULT_WORKFLOWS_COLUMNS: ConfigurableTableHeader[] = [ + { label: 'Status' }, + { label: 'Workflow ID' }, + { label: 'Run ID' }, + { label: 'Type' }, + { label: 'Start' }, + { label: 'End' }, +]; + +const DEFAULT_AVAILABLE_WORKFLOWS_COLUMNS: ConfigurableTableHeader[] = [ + { label: 'History Size' }, + { label: 'History Length' }, + { label: 'Execution Time' }, + { label: 'Execution Duration' }, + { label: 'State Transitions' }, + { label: 'Task Queue' }, + { label: 'Scheduled By ID' }, + { label: 'Scheduled Start Time' }, + { label: 'Deployment' }, + { label: 'Deployment Version' }, + { label: 'Versioning Behavior' }, + { label: 'Change Version' }, +]; + +const DEFAULT_AVAILABLE_WORKFLOWS_COLUMNS_CORE: ConfigurableTableHeader[] = [ + ...DEFAULT_AVAILABLE_WORKFLOWS_COLUMNS, + { label: 'Parent Namespace' }, +]; + +const DEFAULT_SCHEDULES_COLUMNS: ConfigurableTableHeader[] = [ + { label: 'Status' }, + { label: 'Schedule ID' }, + { label: 'Workflow Type' }, + { label: 'Recent Runs' }, + { label: 'Upcoming Runs' }, + { label: 'Schedule Spec' }, +]; + +export const persistedWorkflowTableColumns = persistStore( + 'namespace-workflow-table-columns', + {}, +); + +export const persistedSchedulesTableColumns = persistStore( + 'namespace-schedules-table-columns', + {}, +); + +export const configurableTableColumns: Readable = derived( + [ + namespaces, + page, + persistedWorkflowTableColumns, + persistedSchedulesTableColumns, + ], + ([ + $namespaces, + $page, + $persistedWorkflowTableColumns, + $persistedSchedulesTableColumns, + ]) => { + const state: TableColumns = {}; + const useOrAddDefaultTableColumnsToNamespace = ( + columns: State, + namespace: string, + defaultColumns: ConfigurableTableHeader[], + ) => { + if (!columns?.[namespace]?.length) { + columns[namespace] = [...defaultColumns]; + return columns[namespace]; + } + + return columns[namespace]; + }; + + const getTableColumns = (namespace: string) => ({ + workflows: useOrAddDefaultTableColumnsToNamespace( + $persistedWorkflowTableColumns, + namespace, + DEFAULT_WORKFLOWS_COLUMNS, + ), + schedules: useOrAddDefaultTableColumnsToNamespace( + $persistedSchedulesTableColumns, + namespace, + DEFAULT_SCHEDULES_COLUMNS, + ), + }); + + const namespaceColumns = + $namespaces?.reduce( + (namespaceToColumnsMap, { namespaceInfo: { name } }): TableColumns => { + return { + ...namespaceToColumnsMap, + [name]: getTableColumns(name), + }; + }, + state, + ) ?? {}; + const { namespace: currentNamespace } = $page.params; + + return namespaceColumns[currentNamespace] + ? namespaceColumns + : { + ...namespaceColumns, + [currentNamespace]: getTableColumns(currentNamespace), + }; + }, +); + +export const availableWorkflowSystemSearchAttributeColumns: ( + namespace: string, + settings: Settings, +) => Readable = (namespace, settings) => + derived(configurableTableColumns, ($configurableTableColumns) => + [ + ...DEFAULT_WORKFLOWS_COLUMNS, + ...(settings?.runtimeEnvironment?.isCloud + ? DEFAULT_AVAILABLE_WORKFLOWS_COLUMNS + : DEFAULT_AVAILABLE_WORKFLOWS_COLUMNS_CORE), + ].filter( + (header) => + !$configurableTableColumns[namespace]?.workflows?.some( + (column) => column.label === header.label, + ), + ), + ); + +export const availableScheduleColumns: ( + namespace: string, +) => Readable = (namespace) => + derived(configurableTableColumns, ($configurableTableColumns) => + [...DEFAULT_SCHEDULES_COLUMNS].filter( + (header) => + !$configurableTableColumns[namespace]?.schedules?.some( + (column) => column.label === header.label, + ), + ), + ); + +export const availableCustomSearchAttributeColumns: ( + namespace: string, + table?: ConfigurableTableType, +) => Readable = ( + namespace: string, + table: ConfigurableTableType = TABLE_TYPE.WORKFLOWS, +) => + derived( + [customSearchAttributes, configurableTableColumns], + ([$customSearchAttributes, $configurableTableColumns]) => + Object.keys($customSearchAttributes) + .filter( + (searchAttribute) => + !$configurableTableColumns[namespace]?.[table]?.some( + (column) => column.label === searchAttribute, + ), + ) + .map((key) => ({ + label: key, + })), + ); + +const getDefaultColumns = (table: ConfigurableTableType) => { + switch (table) { + case TABLE_TYPE.WORKFLOWS: + return DEFAULT_WORKFLOWS_COLUMNS; + case TABLE_TYPE.SCHEDULES: + return DEFAULT_SCHEDULES_COLUMNS; + } +}; + +const reducer = (action: Action, state: State): State => { + const defaultColumns = getDefaultColumns(action.payload.table); + switch (action.type) { + case 'CONFIGURABLE_COLUMN.ADD': { + const { label, namespace } = action.payload; + const columns = state?.[namespace] ?? defaultColumns; + + return { + ...state, + [namespace]: [...columns, { label }], + }; + } + case 'CONFIGURABLE_COLUMN.REMOVE': { + const { label: labelToRemove, namespace } = action.payload; + const columns = state?.[namespace] ?? defaultColumns; + + return { + ...state, + [namespace]: columns.filter(({ label }) => label !== labelToRemove), + }; + } + case 'CONFIGURABLE_COLUMN.MOVE': { + const { from, to, namespace } = action.payload; + const columns = state?.[namespace] ?? DEFAULT_WORKFLOWS_COLUMNS; + const tempColumns = [...columns]; + + tempColumns.splice(to, 0, tempColumns.splice(from, 1)[0]); + + return { + ...state, + [namespace]: tempColumns, + }; + } + default: + return state; + } +}; + +const getPersistedColumns = (table: ConfigurableTableType): Writable => { + switch (table) { + case TABLE_TYPE.WORKFLOWS: + return persistedWorkflowTableColumns; + case TABLE_TYPE.SCHEDULES: + return persistedSchedulesTableColumns; + } +}; + +const dispatch = (action: Action) => { + const columns = getPersistedColumns(action.payload.table); + columns.update((previousState) => reducer(action, previousState)); +}; + +export const addColumn = ( + label: AnyWorkflowHeaderLabel, + namespace: string, + table: ConfigurableTableType, +) => { + dispatch({ + type: 'CONFIGURABLE_COLUMN.ADD', + payload: { label, namespace, table }, + }); +}; + +export const removeColumn = ( + label: AnyWorkflowHeaderLabel, + namespace: string, + table: ConfigurableTableType, +) => { + dispatch({ + type: 'CONFIGURABLE_COLUMN.REMOVE', + payload: { label, namespace, table }, + }); +}; + +export const moveColumn = ( + from: number, + to: number, + namespace: string, + table: ConfigurableTableType, +) => { + dispatch({ + type: 'CONFIGURABLE_COLUMN.MOVE', + payload: { from, to, namespace, table }, + }); +}; diff --git a/src/lib/stores/core-user.ts b/src/lib/stores/core-user.ts index 3184c8c4b..7e15e1f55 100644 --- a/src/lib/stores/core-user.ts +++ b/src/lib/stores/core-user.ts @@ -1,9 +1,12 @@ -import { CoreUser, CoreUserKey } from '$lib/models/core-user'; -import { hasContext, getContext } from 'svelte'; -import { readable, Readable } from 'svelte/store'; +import { readable, type Readable } from 'svelte/store'; + +import { getContext, hasContext } from 'svelte'; + +import { type CoreUser, CoreUserKey } from '$lib/models/core-user'; export const defaultCoreUserStore: Readable = readable({ namespaceWriteDisabled: () => false, + isActivityCommandsDisabled: false, }); export const coreUserStore = (): Readable => { diff --git a/src/lib/stores/data-converter-config.ts b/src/lib/stores/data-converter-config.ts index a5ef8c8d7..bf66faf3c 100644 --- a/src/lib/stores/data-converter-config.ts +++ b/src/lib/stores/data-converter-config.ts @@ -1,7 +1,6 @@ import { writable } from 'svelte/store'; -import { persistStore } from '$lib/stores/persist-store'; -export const dataConverterPort = persistStore('port', null, true); +import type { DataEncoderStatus } from '$lib/types/global'; export const lastDataConverterStatus = writable('notRequested'); diff --git a/src/lib/stores/data-encoder-config.ts b/src/lib/stores/data-encoder-config.ts index 54157e99a..45a39e750 100644 --- a/src/lib/stores/data-encoder-config.ts +++ b/src/lib/stores/data-encoder-config.ts @@ -1,7 +1,10 @@ import { writable } from 'svelte/store'; + import { persistStore } from '$lib/stores/persist-store'; +import type { DataEncoderStatus } from '$lib/types/global'; +import { has } from '$lib/utilities/has'; -export const codecEndpoint = persistStore('endpoint', null, true); +export const codecEndpoint = persistStore('endpoint', null, true); export const passAccessToken = persistStore( 'passAccessToken', @@ -9,11 +12,31 @@ export const passAccessToken = persistStore( true, ); +export const includeCredentials = persistStore( + 'includeCredentials', + false, + true, +); + +export const overrideRemoteCodecConfiguration = persistStore( + 'overrideRemoteCodecConfiguration', + false, + true, +); + export const lastDataEncoderStatus = writable('notRequested'); -export function setLastDataEncoderFailure(): void { +export const lastDataEncoderError = writable(''); + +export function setLastDataEncoderFailure(error?: unknown): void { lastDataEncoderStatus.set('error'); + + if (error && has(error, 'message') && typeof error.message === 'string') { + lastDataEncoderError.set(error.message); + } else { + lastDataEncoderError.set(''); + } } export function setLastDataEncoderSuccess(): void { diff --git a/src/lib/stores/data-encoder.test.ts b/src/lib/stores/data-encoder.test.ts new file mode 100644 index 000000000..f88f0974e --- /dev/null +++ b/src/lib/stores/data-encoder.test.ts @@ -0,0 +1,87 @@ +import { get } from 'svelte/store'; + +import { beforeEach, describe, expect, it } from 'vitest'; + +import { authUser } from './auth-user'; +import { dataEncoder } from './data-encoder'; +import { + codecEndpoint, + overrideRemoteCodecConfiguration, +} from './data-encoder-config'; + +const clearLocalStorageAndStores = () => { + localStorage.clear(); + codecEndpoint.set(''); + overrideRemoteCodecConfiguration.set(false); +}; + +describe('dataEncoder', () => { + beforeEach(clearLocalStorageAndStores); + + it('should set default values', () => { + expect(get(dataEncoder)).toEqual({ + accessToken: undefined, + endpoint: '', + hasError: false, + hasNotRequested: true, + hasSuccess: false, + namespace: 'default', + settingsEndpoint: '', + settingsIncludeCredentials: false, + settingsPassAccessToken: false, + customErrorLink: '', + customErrorMessage: '', + }); + }); + + it('should set access token from authUser', () => { + authUser.set({ accessToken: 'abc' }); + expect(get(dataEncoder)).toEqual({ + accessToken: 'abc', + endpoint: '', + hasError: false, + hasNotRequested: true, + hasSuccess: false, + namespace: 'default', + settingsEndpoint: '', + settingsIncludeCredentials: false, + settingsPassAccessToken: false, + customErrorLink: '', + customErrorMessage: '', + }); + }); + + it('should set access token from authUser', () => { + authUser.set({ accessToken: 'abc' }); + expect(get(dataEncoder)).toEqual({ + accessToken: 'abc', + endpoint: '', + hasError: false, + hasNotRequested: true, + hasSuccess: false, + namespace: 'default', + settingsEndpoint: '', + settingsIncludeCredentials: false, + settingsPassAccessToken: false, + customErrorLink: '', + customErrorMessage: '', + }); + }); + + it('should set codecEndpoint', () => { + codecEndpoint.set('https://localhost:8383'); + expect(get(dataEncoder)).toEqual({ + accessToken: 'abc', + endpoint: 'https://localhost:8383', + hasError: false, + hasNotRequested: true, + hasSuccess: false, + namespace: 'default', + settingsEndpoint: '', + settingsIncludeCredentials: false, + settingsPassAccessToken: false, + customErrorLink: '', + customErrorMessage: '', + }); + }); +}); diff --git a/src/lib/stores/data-encoder.ts b/src/lib/stores/data-encoder.ts index 1dcae7e0f..3e77bf1cd 100644 --- a/src/lib/stores/data-encoder.ts +++ b/src/lib/stores/data-encoder.ts @@ -1,32 +1,62 @@ import { derived } from 'svelte/store'; + import { page } from '$app/stores'; -import { - dataConverterPort, - lastDataConverterStatus, -} from './data-converter-config'; -import { codecEndpoint, lastDataEncoderStatus } from './data-encoder-config'; + import { authUser } from './auth-user'; +import { lastDataConverterStatus } from './data-converter-config'; +import { + codecEndpoint, + lastDataEncoderStatus, + overrideRemoteCodecConfiguration, +} from './data-encoder-config'; + +type DataEncoder = { + namespace: string; + settingsEndpoint?: string; + settingsPassAccessToken: boolean; + settingsIncludeCredentials: boolean; + endpoint: string; + customErrorMessage: string; + customErrorLink: string; + accessToken?: string; + hasNotRequested: boolean; + hasError: boolean; + hasSuccess: boolean; +}; export const dataEncoder = derived( [ page, codecEndpoint, + overrideRemoteCodecConfiguration, lastDataEncoderStatus, - dataConverterPort, lastDataConverterStatus, authUser, ], ([ $page, $codecEndpoint, + $overrideRemoteCodecConfiguration, $lastDataEncoderStatus, - $dataConverterPort, $lastDataConverterStatus, $authUser, - ]) => { + ]): DataEncoder => { const namespace = $page.params.namespace; - const settingsEndpoint = $page?.stuff?.settings?.codec?.endpoint; - const endpoint = $codecEndpoint || settingsEndpoint; + const settingsEndpoint = $page?.data?.settings?.codec?.endpoint; + const customErrorMessage = + $page?.data?.settings?.codec?.customErrorMessage?.default?.message || ''; + const customErrorLink = + $page?.data?.settings?.codec?.customErrorMessage?.default?.link || ''; + + const settingsPassAccessToken = Boolean( + $page?.data?.settings?.codec?.passAccessToken, + ); + const settingsIncludeCredentials = Boolean( + $page?.data?.settings?.codec?.includeCredentials, + ); + const endpoint = $overrideRemoteCodecConfiguration + ? $codecEndpoint + : settingsEndpoint || $codecEndpoint; const accessToken = $authUser?.accessToken; const hasNotRequested = endpoint ? $lastDataEncoderStatus === 'notRequested' @@ -37,19 +67,19 @@ export const dataEncoder = derived( const hasSuccess = endpoint ? $lastDataEncoderStatus === 'success' : $lastDataConverterStatus === 'success'; - const hasEndpointAndPortConfigured = endpoint && $dataConverterPort; - const hasEndpointOrPortConfigured = endpoint || $dataConverterPort; return { namespace, settingsEndpoint, + settingsPassAccessToken, + settingsIncludeCredentials, endpoint, accessToken, + customErrorMessage, + customErrorLink, hasNotRequested, hasError, hasSuccess, - hasEndpointAndPortConfigured, - hasEndpointOrPortConfigured, }; }, ); diff --git a/src/lib/stores/error.ts b/src/lib/stores/error.ts index d6d9c37d7..e347e96bd 100644 --- a/src/lib/stores/error.ts +++ b/src/lib/stores/error.ts @@ -1,3 +1,5 @@ import { writable } from 'svelte/store'; +import type { NetworkError } from '$lib/types/global'; + export const networkError = writable(null); diff --git a/src/lib/stores/event-view.ts b/src/lib/stores/event-view.ts index bd64cf6d5..4552ecf35 100644 --- a/src/lib/stores/event-view.ts +++ b/src/lib/stores/event-view.ts @@ -1,9 +1,6 @@ -import { derived, Readable } from 'svelte/store'; -import { page } from '$app/stores'; import { persistStore } from '$lib/stores/persist-store'; -import { settings } from '$lib/stores/settings'; -import { temporalVersion } from './versions'; -import { isVersionNewer } from '$lib/utilities/version-check'; +import type { EventView } from '$lib/types/events'; +import type { BooleanString } from '$lib/types/global'; export type EventSortOrder = 'ascending' | 'descending'; export type EventSortOrderOptions = { @@ -16,40 +13,15 @@ export const autoRefreshWorkflow = persistStore<'on' | 'off'>( 'off', ); -export const eventViewType = persistStore('eventView', 'feed'); +export const eventViewType = persistStore('eventView', 'feed', true); -export const expandAllEvents = persistStore('expandAllEvents', 'false'); +export const minimizeEventView = persistStore('minimizeEventView', true, true); export const eventFilterSort = persistStore( 'eventFilterSort', 'descending', ); - export const eventShowElapsed = persistStore( 'eventShowElapsed', 'false', ); - -export const eventCategoryParam = derived([page], ([$page]) => - $page.url.searchParams.get('category'), -); - -export const supportsReverseOrder = derived( - [temporalVersion, settings], - ([$temporalVersion, $settings]) => { - if ($settings.runtimeEnvironment.isCloud) return true; - - return isVersionNewer($temporalVersion, '1.16.0'); - }, -); - -export const eventSortOrder: Readable = derived( - [eventFilterSort, supportsReverseOrder], - ([$eventFilterSort, $supportsReverseOrder]) => { - let sortOrder: EventSortOrder = 'ascending'; - if ($supportsReverseOrder) { - sortOrder = $eventFilterSort; - } - return sortOrder; - }, -); diff --git a/src/lib/stores/events.ts b/src/lib/stores/events.ts index fdc87bb5e..bb5d2f470 100644 --- a/src/lib/stores/events.ts +++ b/src/lib/stores/events.ts @@ -1,32 +1,23 @@ -import { - derived, - readable, - writable, - Readable, - Writable, - get, -} from 'svelte/store'; -import type { StartStopNotifier } from 'svelte/store'; +import { derived, type Readable, writable } from 'svelte/store'; import { page } from '$app/stores'; -import { - fetchEvents, +import type { FetchEventsParameters, FetchEventsParametersWithSettings, } from '$lib/services/events-service'; -import { eventCategoryParam, eventSortOrder } from './event-view'; -import { decodeURIForSvelte } from '$lib/utilities/encode-uri'; -import { withLoading, delay } from '$lib/utilities/stores/with-loading'; -import { groupEvents } from '$lib/models/event-groups'; -import { refresh } from '$lib/stores/workflow-run'; import { authUser } from '$lib/stores/auth-user'; -import { previous } from '$lib/stores/previous-events'; +import { refresh } from '$lib/stores/workflow-run'; +import type { WorkflowEvents } from '$lib/types/events'; +import { decodeURIForSvelte } from '$lib/utilities/encode-uri'; +import { + isLocalActivityMarkerEvent, + isResetEvent, +} from '$lib/utilities/is-event-type'; -const emptyEvents: FetchEventsResponse = { - events: [], - eventGroups: [], -}; +import { eventFilterSort } from './event-view'; +import { eventTypeFilter } from './filters'; +import { persistStore } from './persist-store'; const namespace = derived([page], ([$page]) => { if ($page.params.namespace) { @@ -49,39 +40,15 @@ const runId = derived([page], ([$page]) => { return ''; }); -const settings = derived([page], ([$page]) => $page.stuff.settings); +const settings = derived([page], ([$page]) => $page.data?.settings); const accessToken = derived( [authUser], ([$authUser]) => $authUser?.accessToken, ); -const isNewRequest = ( - params: FetchEventsParameters, - previous: Writable, -): boolean => { - for (const required of ['namespace', 'workflowId', 'runId']) { - if (!params[required]) return false; - } - - let matchedPrevious = true; - const previousParameters = get(previous); - for (const key of Object.keys(previousParameters)) { - if (previousParameters[key] !== params[key]) { - matchedPrevious = false; - break; - } - } - - if (matchedPrevious) return false; - - previous.set(params); - - return true; -}; - export const parameters: Readable = derived( - [namespace, workflowId, runId, eventSortOrder], + [namespace, workflowId, runId, eventFilterSort], ([$namespace, $workflowId, $runId, $sort]) => { return { namespace: $namespace, @@ -106,74 +73,39 @@ export const parametersWithSettings: Readable }, ); -export const updateEventHistory: StartStopNotifier = ( - set, -) => { - return parametersWithSettings.subscribe(async (params) => { - const { settings: _, ...rest } = params; - if (isNewRequest(rest, previous)) { - withLoading(loading, updating, async () => { - const events = await fetchEvents(params); - if (events?.events?.length) { - set(events); - } else { - setTimeout(() => { - set(events); - }, delay); - } - }); - } - }); -}; - -export const eventHistory = readable(emptyEvents, updateEventHistory); - export const timelineEvents = writable(null); - -export const events: Readable = derived( - [eventHistory, eventCategoryParam, timelineEvents], - ([$eventHistory, $category, $timelineEvents]) => { - if ($timelineEvents) { - return $timelineEvents; - } - const { events } = $eventHistory; - if (!$category) return events; - return events.filter((event) => event.category === $category); +export const fullEventHistory = writable([]); + +export const pauseLiveUpdates = writable(false); +export const currentEventHistory = writable([]); + +export const filteredEventHistory = derived( + [currentEventHistory, eventTypeFilter], + ([$history, $types]) => { + return $history.filter((event) => { + if (isLocalActivityMarkerEvent(event)) { + return $types.includes('local-activity'); + } + return $types.includes(event.category); + }); }, ); -export const eventGroups: Readable = derived( - [eventHistory, eventCategoryParam], - ([$eventHistory, $category]) => { - const { eventGroups } = $eventHistory; - if (!$category) return eventGroups; - return eventGroups.filter((event) => event.category === $category); - }, +export const resetEvents = derived(fullEventHistory, (events) => + events.filter(isResetEvent), ); -export const ascendingEventGroups: Readable = derived( - [eventHistory, eventSortOrder, eventCategoryParam], - ([$eventHistory, $sortOrder, $category]) => { - const { events } = $eventHistory; - const _events = - $sortOrder === 'descending' ? events.slice().reverse() : events; - const eventGroups = groupEvents(_events); - if (!$category) return eventGroups; - return eventGroups.filter((event) => event.category === $category); - }, +export const decodeEventHistory = persistStore( + 'decodeEventHistory', + true, + true, ); -export const ascendingEvents: Readable = derived( - [eventHistory, eventSortOrder, eventCategoryParam], - ([$eventHistory, $sortOrder, $category]) => { - const { events } = $eventHistory; - const _events = - $sortOrder === 'descending' ? events.slice().reverse() : events; - if (!$category) return _events; - return _events.filter((event) => event.category === $category); - }, -); +export type DownloadEventHistorySetting = 'encoded' | 'decoded' | 'readable'; -export const updating = writable(true); -export const loading = writable(true); -export const activeEvent = writable(null); +export const downloadEventHistorySetting = + persistStore( + 'downloadEventHistorySetting', + 'encoded', + true, + ); diff --git a/src/lib/stores/fiction-store.ts b/src/lib/stores/fiction-store.ts deleted file mode 100644 index e63b92229..000000000 --- a/src/lib/stores/fiction-store.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { writable } from 'svelte/store'; - -export const currentProps = writable<{ [index: string]: unknown }>({}); diff --git a/src/lib/stores/filters.ts b/src/lib/stores/filters.ts index df06ead34..6f0e6b1ee 100644 --- a/src/lib/stores/filters.ts +++ b/src/lib/stores/filters.ts @@ -1,8 +1,116 @@ -import type { - WorkflowFilter, - WorkflowSort, -} from '$lib/models/workflow-filters'; -import { writable } from 'svelte/store'; - -export const workflowFilters = writable([]); -export const workflowSorts = writable([]); +import type { StartStopNotifier } from 'svelte/store'; +import { derived, get, writable } from 'svelte/store'; + +import { page } from '$app/stores'; + +import { allEventTypeOptions } from '$lib/models/event-history/get-event-categorization'; +import type { SearchAttributeFilter } from '$lib/models/search-attribute-filters'; +import { persistStore } from '$lib/stores/persist-store'; +import type { EventClassification, EventTypeCategory } from '$lib/types/events'; + +export const query = derived([page], ([$page]) => + $page.url.searchParams.get('query'), +); + +export const showChildWorkflows = persistStore( + 'showChildWorkflows', + true, + true, +); + +export const hideChildWorkflows = persistStore( + 'hideChildWorkflows', + false, + true, +); + +const category = derived([page], ([$page]) => + $page.url.searchParams.get('category'), +); +const classification = derived([page], ([$page]) => + $page.url.searchParams.get('classification'), +); + +const parameters = derived( + [query, category, classification], + ([$query, $category, $classification]) => { + return { + query: $query, + category: $category, + classification: $classification, + }; + }, +); + +const updateWorkflowFilters: StartStopNotifier = ( + set, +) => { + return parameters.subscribe(({ query }) => { + if (!query && get(workflowFilters).length) { + // Clear filters if there is no query + set([]); + } + }); +}; + +export const searchInputViewOpen = persistStore( + 'searchInputView', + false, + true, +); + +export const workflowFilters = writable( + [], + updateWorkflowFilters, +); + +const updateScheduleFilters: StartStopNotifier = ( + set, +) => { + return parameters.subscribe(({ query }) => { + if (!query && get(scheduleFilters).length) { + // Clear filters if there is no query + set([]); + } + }); +}; + +export const scheduleFilters = writable( + [], + updateScheduleFilters, +); + +const updateEventCategoryFilter: StartStopNotifier< + EventTypeCategory[] | null +> = (set) => { + return parameters.subscribe(({ category }) => { + if (!category && get(eventCategoryFilter)) { + // Clear filter if there is no category + set(null); + } + }); +}; + +export const eventCategoryFilter = writable( + undefined, + updateEventCategoryFilter, +); + +const updateEventClassificationFilter: StartStopNotifier< + EventClassification[] | null +> = (set) => { + return parameters.subscribe(({ classification }) => { + if (!classification && get(eventClassificationFilter)) { + // Clear filter if there is no category + set(null); + } + }); +}; + +export const eventClassificationFilter = writable< + EventClassification[] | undefined +>(undefined, updateEventClassificationFilter); + +const defaultOptions = allEventTypeOptions.map(({ value }) => value); +export const eventTypeFilter = writable(defaultOptions); +export const eventStatusFilter = writable(false); diff --git a/src/lib/stores/import-events.test.ts b/src/lib/stores/import-events.test.ts new file mode 100644 index 000000000..45af7f07f --- /dev/null +++ b/src/lib/stores/import-events.test.ts @@ -0,0 +1,12 @@ +import { get } from 'svelte/store'; + +import { describe, expect, it } from 'vitest'; + +import { importEventGroups, importEvents } from './import-events'; + +describe('ImportEvents', () => { + it('should get default values', () => { + expect(get(importEvents)).toEqual([]); + expect(get(importEventGroups)).toEqual([]); + }); +}); diff --git a/src/lib/stores/import-events.ts b/src/lib/stores/import-events.ts index fcbd01c19..b65bb549e 100644 --- a/src/lib/stores/import-events.ts +++ b/src/lib/stores/import-events.ts @@ -1,4 +1,7 @@ import { writable } from 'svelte/store'; +import type { EventGroups } from '$lib/models/event-groups/event-groups'; +import type { WorkflowEvents } from '$lib/types/events'; + export const importEvents = writable([]); export const importEventGroups = writable([]); diff --git a/src/lib/stores/labs-mode.ts b/src/lib/stores/labs-mode.ts new file mode 100644 index 000000000..7dffc9b84 --- /dev/null +++ b/src/lib/stores/labs-mode.ts @@ -0,0 +1,3 @@ +import { persistStore } from './persist-store'; + +export const labsMode = persistStore('labsMode', false, true); diff --git a/src/lib/stores/namespaces.test.ts b/src/lib/stores/namespaces.test.ts new file mode 100644 index 000000000..b5c6247d6 --- /dev/null +++ b/src/lib/stores/namespaces.test.ts @@ -0,0 +1,49 @@ +import { get } from 'svelte/store'; + +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { lastUsedNamespace, namespaces } from './namespaces'; + +describe('lastUsedNamespace', () => { + beforeEach(() => { + localStorage.clear(); + }); + + afterEach(() => { + localStorage.clear(); + vi.restoreAllMocks(); + }); + + it('should default to "default"', () => { + expect(get(lastUsedNamespace)).toEqual('default'); + }); + + it('should be able to set a value', () => { + lastUsedNamespace.set('test'); + expect(get(lastUsedNamespace)).toEqual('test'); + }); + + it('should store values in localStorage', () => { + lastUsedNamespace.set('test'); + expect(localStorage.getItem('lastNamespace')).toMatch('test'); + }); + + it('should store values in localStorage', () => { + lastUsedNamespace.set('test'); + expect(localStorage.setItem).toHaveBeenCalledWith( + 'lastNamespace', + JSON.stringify('test'), + ); + }); +}); + +describe('namespaces', () => { + beforeEach(() => { + namespaces.set([]); + }); + + it('should be able to set a value', () => { + namespaces.set([{ name: 'default' }]); + expect(get(namespaces)).toEqual([{ name: 'default' }]); + }); +}); diff --git a/src/lib/stores/namespaces.ts b/src/lib/stores/namespaces.ts index d25c1b653..4abcbe35b 100644 --- a/src/lib/stores/namespaces.ts +++ b/src/lib/stores/namespaces.ts @@ -1,7 +1,9 @@ -import type { DescribeNamespaceResponse } from '$types'; import { writable } from 'svelte/store'; + +import type { DescribeNamespaceResponse } from '$lib/types'; + import { persistStore } from './persist-store'; -export const lastUsedNamespace = persistStore('lastNamespace', 'default'); +export const lastUsedNamespace = persistStore('lastNamespace', 'default', true); export const namespaces = writable([]); diff --git a/src/lib/stores/nav-open.ts b/src/lib/stores/nav-open.ts index b41b423d0..1a224e60c 100644 --- a/src/lib/stores/nav-open.ts +++ b/src/lib/stores/nav-open.ts @@ -1,6 +1,7 @@ -import { persistStore } from './persist-store'; import { writable } from 'svelte/store'; +import { persistStore } from './persist-store'; + export const navOpen = persistStore('navOpen', false); export const namespaceSelectorOpen = writable(); diff --git a/src/lib/stores/new-feature-tags.ts b/src/lib/stores/new-feature-tags.ts index fd227a983..f5ae0709e 100644 --- a/src/lib/stores/new-feature-tags.ts +++ b/src/lib/stores/new-feature-tags.ts @@ -1,4 +1,5 @@ import { get } from 'svelte/store'; + import { persistStore } from './persist-store'; export const viewedFeatureTags = persistStore('viewedFeatureTags', null); diff --git a/src/lib/stores/notifications.test.ts b/src/lib/stores/notifications.test.ts deleted file mode 100644 index 562ced7a3..000000000 --- a/src/lib/stores/notifications.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { afterEach, describe, expect, it } from 'vitest'; -import { get } from 'svelte/store'; -import { notifications } from './notifications'; - -describe('notifications', () => { - afterEach(() => { - notifications.clear(); - }); - - it('should have a subscribe function', () => { - expect(notifications.subscribe).toBeDefined(); - }); - - it('should start with an empty array', () => { - expect(get(notifications)).toEqual([]); - }); - - describe('add', () => { - it('should increase the length by 1', () => { - notifications.add('error', 'This is an error'); - expect(get(notifications)).toHaveLength(1); - }); - - it('should have the correct data', () => { - notifications.add('error', 'This is an error'); - const [notification] = get(notifications); - - expect(notification.type).toBe('error'); - expect(notification.message).toBe('This is an error'); - }); - }); - - describe('clear', () => { - it('should clear out all of the notifications', () => { - notifications.add('error', 'This is an error'); - notifications.add('error', 'This is an error'); - notifications.add('error', 'This is an error'); - - notifications.clear(); - - expect(get(notifications)).toHaveLength(0); - }); - }); - - describe('dismiss', () => { - it('should remove a notification', () => { - notifications.add('error', 'This is an error'); - notifications.add('success', 'Everything went well'); - - const [notification] = get(notifications); - const { id } = notification; - - notifications.dismiss(id); - - expect(get(notifications)).toHaveLength(1); - }); - - it('should remove a the correct notification', () => { - notifications.add('error', 'This is an error'); - notifications.add('success', 'Everything went well'); - - const [notification] = get(notifications); - const { id } = notification; - - notifications.dismiss(id); - - expect(get(notifications)).not.toContain(notification); - }); - }); -}); diff --git a/src/lib/stores/notifications.ts b/src/lib/stores/notifications.ts deleted file mode 100644 index b4e8c1e5f..000000000 --- a/src/lib/stores/notifications.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { writable } from 'svelte/store'; -import type { Readable } from 'svelte/store'; - -type NotificationType = 'error' | 'warning' | 'success' | 'information'; -type Notification = { - id: string; - type: NotificationType; - message: string; - expiration: number; -}; -type Notifications = Notification[]; - -const whenIdle = - globalThis?.requestIdleCallback || - ((fn: Parameters[0]) => { - setTimeout(fn, 0); - }); - -const store = writable([], () => { - const interval = setInterval(() => { - whenIdle(() => { - const now = Date.now(); - store.update((ns) => { - return ns.filter((n) => n.expiration < now); - }); - }); - }, 5000); - - return () => { - clearInterval(interval); - }; -}); - -const createNotification = ( - type: NotificationType, - message: string, - duration = 30, -): Notification => { - const now = Date.now(); - - return { - id: String(now + Math.random()), - type, - message, - expiration: now + duration * 1000, - }; -}; - -const add = (type: NotificationType, message: string, duration = 30): void => { - store.update((ns) => [...ns, createNotification(type, message, duration)]); -}; - -const dismiss = (id: string): void => { - store.update((ns) => ns.filter((n) => n.id !== id)); -}; - -const clear = (): void => { - store.set([]); -}; - -export const notifications: Readable & { - add: typeof add; - dismiss: typeof dismiss; - clear: typeof clear; -} = { - subscribe: store.subscribe, - add, - dismiss, - clear, -}; diff --git a/src/lib/stores/pagination.test.ts b/src/lib/stores/pagination.test.ts index 6614dd9ad..08a52374d 100644 --- a/src/lib/stores/pagination.test.ts +++ b/src/lib/stores/pagination.test.ts @@ -1,6 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { describe, expect, it } from 'vitest'; import { get } from 'svelte/store'; + +import { describe, expect, it } from 'vitest'; + import { getIndex, getPageForIndex, @@ -10,7 +12,6 @@ import { pagination, perPageFromSearchParameter, } from './pagination'; -import { stringifyWithBigInt } from '../utilities/parse-with-big-int'; const oneHundredResolutions = new Array(100).fill(null).map((_, i) => i); @@ -273,6 +274,160 @@ describe('pagination', () => { expect(initialItem).toBe(0); }); + + it('should not have default active row index', () => { + const store = pagination(oneHundredResolutions, 5); + const { activeRowIndex } = get(store); + + expect(activeRowIndex).toBe(undefined); + }); + + it('should go to the first row', () => { + const store = pagination(oneHundredResolutions, 5); + + expect(get(store).activeRowIndex).toBe(undefined); + + store.nextRow(); + + expect(get(store).activeRowIndex).toBe(0); + }); + + it('should go to the next row', () => { + const store = pagination(oneHundredResolutions, 5); + + expect(get(store).activeRowIndex).toBe(undefined); + + store.nextRow(); + + expect(get(store).activeRowIndex).toBe(0); + + store.nextRow(); + + expect(get(store).activeRowIndex).toBe(1); + + for (let i = 0; i < 2; i++) { + store.nextRow(); + } + + expect(get(store).activeRowIndex).toBe(3); + }); + + it('should go to the previous row', () => { + const store = pagination(oneHundredResolutions, 5); + + for (let i = 0; i < 4; i++) { + store.nextRow(); + } + + expect(get(store).activeRowIndex).toBe(3); + + for (let i = 0; i < 2; i++) { + store.previousRow(); + } + + expect(get(store).activeRowIndex).toBe(1); + }); + + it('should not exceed pageSize on next row', () => { + const store = pagination(oneHundredResolutions, 5); + + for (let i = 0; i < 10; i++) { + store.nextRow(); + } + + expect(get(store).activeRowIndex).toBe(4); + }); + + it('should not exceed items on page on next row', () => { + const store = pagination([1, 2], 5); + + for (let i = 0; i < 10; i++) { + store.nextRow(); + } + + expect(get(store).activeRowIndex).toBe(1); + }); + + it('should set active row to undefined on next page', () => { + const store = pagination(oneHundredResolutions, 5); + + for (let i = 0; i < 4; i++) { + store.nextRow(); + } + + expect(get(store).activeRowIndex).toBe(3); + + store.next(); + + expect(get(store).activeRowIndex).toBe(undefined); + }); + + it('should set active row to last row item on next if next page has less items', () => { + const store = pagination([1, 2, 3, 4, 5, 6, 7, 8], 5); + + for (let i = 0; i < 5; i++) { + store.nextRow(); + } + + expect(get(store).activeRowIndex).toBe(4); + + store.jumpToIndex(6); + + expect(get(store).activeRowIndex).toBe(2); + }); + + it('should not go below first index of page on previous row', () => { + const store = pagination(oneHundredResolutions, 5); + + store.nextRow(); + store.nextRow(); + + for (let i = 0; i <= 10; i++) { + store.previousRow(); + } + + expect(get(store).activeRowIndex).toBe(0); + }); + + it('by default should not set active row index', () => { + const store = pagination(oneHundredResolutions, 5); + + expect(get(store).activeRowIndex).toBe(undefined); + + store.setActiveRowIndex(); + + expect(get(store).activeRowIndex).toBe(undefined); + }); + + it('should set active row index', () => { + const store = pagination(oneHundredResolutions, 5); + + expect(get(store).activeRowIndex).toBe(undefined); + + store.setActiveRowIndex(4); + + expect(get(store).activeRowIndex).toBe(4); + }); + + it('should not exceed pageSize on set active row index', () => { + const store = pagination(oneHundredResolutions, 5); + + expect(get(store).activeRowIndex).toBe(undefined); + + store.setActiveRowIndex(8); + + expect(get(store).activeRowIndex).toBe(undefined); + }); + + it('should not exceed item size on set active row index', () => { + const store = pagination([1, 2], 5); + + expect(get(store).activeRowIndex).toBe(undefined); + + store.setActiveRowIndex(8); + + expect(get(store).activeRowIndex).toBe(undefined); + }); }); describe('getPageForIndex', () => { @@ -340,6 +495,10 @@ describe('getStartingIndexForPage', () => { expect(getStartingIndexForPage(100, 20, oneHundredResolutions)).toBe(80); }); + it('should return 0 for the something out of bounds if the total number of items is less than itemsPerPage', () => { + expect(getStartingIndexForPage(3, 101, oneHundredResolutions)).toBe(0); + }); + it('should return 0 if given a negative number for the page', () => { expect(getStartingIndexForPage(-10, 20, oneHundredResolutions)).toBe(0); }); @@ -383,3 +542,29 @@ describe('perPageFromSearchParameter', () => { expect(perPageFromSearchParameter({} as any)).toBe(100); }); }); + +describe('getStartingIndexForPage', () => { + it('should return 0 for the first page', () => { + expect(getStartingIndexForPage(1, 20, oneHundredResolutions)).toBe(0); + }); + + it('should return the first index of the second page for the something on the second page', () => { + expect(getStartingIndexForPage(2, 20, oneHundredResolutions)).toBe(20); + }); + + it('should return the first index of the last page for the something out of bounds', () => { + expect(getStartingIndexForPage(100, 20, oneHundredResolutions)).toBe(80); + }); + + it('should return 0 for the something out of bounds if the total number of items is less than itemsPerPage', () => { + expect(getStartingIndexForPage(3, 101, oneHundredResolutions)).toBe(0); + }); + + it('should return 0 if given a negative number for the page', () => { + expect(getStartingIndexForPage(-10, 20, oneHundredResolutions)).toBe(0); + }); + + it('should return 0 if given NaN', () => { + expect(getStartingIndexForPage(NaN, 20, oneHundredResolutions)).toBe(0); + }); +}); diff --git a/src/lib/stores/pagination.ts b/src/lib/stores/pagination.ts index 1d903b970..f2728bf69 100644 --- a/src/lib/stores/pagination.ts +++ b/src/lib/stores/pagination.ts @@ -1,8 +1,12 @@ -import { derived, writable, get } from 'svelte/store'; import type { Readable } from 'svelte/store'; +import { derived, get, writable } from 'svelte/store'; + +import { has } from '$lib/utilities/has'; export const defaultItemsPerPage = 100; export const options: string[] = ['100', '250', '500']; +export const perPageKey = 'per-page'; +export const currentPageKey = 'page'; export const MAX_PAGE_SIZE = options[options.length - 1]; type PaginationMethods = { @@ -10,9 +14,13 @@ type PaginationMethods = { next: () => void; previous: () => void; jumpToPage: (x: number | string) => void; + jumpToHashPage: (hash: string) => void; jumpToIndex: (x: number | string) => void; findIndex: (fn: (item: T) => boolean) => number; findPage: (fn: (item: T) => boolean) => number; + nextRow: () => void; + previousRow: () => void; + setActiveRowIndex: (activeRowIndex: number | undefined) => void; }; type PaginationStore = PaginationMethods & @@ -27,12 +35,56 @@ type PaginationStore = PaginationMethods & pageSize: number; currentPage: number; totalPages: number; + activeRowIndex: number | undefined; + pageShortcuts: number[]; }>; export const getPageForIndex = (i: number, pageSize: number): number => { return Math.floor(i / pageSize) + 1; }; +export const getPageShortcuts = ( + currentPage: number, + totalPages: number, +): number[] => { + if (totalPages <= 9) { + return new Array(totalPages).fill(0).map((_, idx) => idx + 1); + } + + if (currentPage < 5) { + return [1, 2, 3, 4, 5, NaN, totalPages - 2, totalPages - 1, totalPages]; + } + + if (currentPage >= 5 && currentPage <= totalPages - 5) { + return [ + 1, + 2, + NaN, + currentPage - 1, + currentPage, + currentPage + 1, + NaN, + totalPages - 1, + totalPages, + ]; + } + + if (currentPage >= totalPages - 5) { + return [ + 1, + 2, + NaN, + totalPages - 5, + totalPages - 4, + totalPages - 3, + totalPages - 2, + totalPages - 1, + totalPages, + ]; + } + return []; +}; + export const getStartingIndexForPage = ( page: number, itemsPerPage: number, @@ -41,8 +93,10 @@ export const getStartingIndexForPage = ( if (isNaN(page)) return 0; if (page <= 1) return 0; - if (page > getTotalPages(itemsPerPage, items)) - return items.length - itemsPerPage; + if (page > getTotalPages(itemsPerPage, items)) { + const index = items.length - itemsPerPage; + return index > 0 ? index : 0; + } return Math.floor(itemsPerPage * (page - 1)); }; @@ -56,6 +110,13 @@ export const getNearestStartingIndex = ( return getStartingIndexForPage(page, itemsPerPage, items); }; +export const getMaxRowIndex = ( + itemsOfPage: number, + itemsPerPage: number, +): number => { + return Math.min(itemsOfPage - 1, itemsPerPage - 1); +}; + export const getValidPage = ( page: number, itemsPerPage: number, @@ -99,24 +160,22 @@ export const outOfBounds = ( export const pagination = ( items: Readonly = [], perPage: number | string = defaultItemsPerPage, - startingIndex: string | number = 0, + currentPage: string | number = 0, ): PaginationStore => { perPage = perPageFromSearchParameter(perPage); - const start = getNearestStartingIndex( - toNumber(startingIndex), - perPage, - items, - ); + const start = getNearestStartingIndex(toNumber(currentPage), perPage, items); const pageSize = writable(perPage); const index = writable(start); + const activeRowIndex = writable(undefined); const adjustPageSize = (n: number | string) => { pageSize.set(toNumber(n)); }; const next = () => { + setActiveRowIndex(); index.update((index) => { const nextIndex = index + get(pageSize); if (outOfBounds(nextIndex, items)) return index; @@ -125,6 +184,7 @@ export const pagination = ( }; const previous = () => { + setActiveRowIndex(); index.update((index) => { const nextStart = index - get(pageSize); return getIndex(nextStart, items); @@ -133,9 +193,19 @@ export const pagination = ( const jumpToPage = (page: number | string) => { const itemsPerPage = get(pageSize); - return index.set( - getStartingIndexForPage(Number(page), itemsPerPage, items), + const nextIndex = getStartingIndexForPage( + Number(page), + itemsPerPage, + items, ); + const pageItemSize = items.slice( + nextIndex, + nextIndex + itemsPerPage, + ).length; + if (get(activeRowIndex) > pageItemSize - 1) { + activeRowIndex.set(pageItemSize - 1); + } + return index.set(nextIndex); }; const jumpToIndex = (i: number | string) => { @@ -143,6 +213,21 @@ export const pagination = ( jumpToPage(page); }; + const jumpToHashPage = (hash: string) => { + const hashId = hash?.slice(1); + if (hashId) { + const itemIndex = items.findIndex( + (item: unknown) => has(item, 'id') && item?.id === hashId, + ); + if (itemIndex !== -1) { + const hashPage = getPageForIndex(itemIndex, get(pageSize)); + if (hashPage !== currentPage) { + jumpToPage(hashPage); + } + } + } + }; + const findIndex = (fn: (item: T) => boolean): number => { for (let i = 0; i < items.length; i++) { if (fn(items[i])) return i; @@ -154,20 +239,57 @@ export const pagination = ( return getPageForIndex(i, get(pageSize)); }; - const { subscribe } = derived([index, pageSize], ([$index, $pageSize]) => { - return { - items: items.slice($index, $index + $pageSize), - initialItem: items[0], - hasPrevious: !outOfBounds($index - $pageSize, items), - hasNext: !outOfBounds($index + $pageSize, items), - startingIndex: $index, - endingIndex: getIndex($index + $pageSize - 1, items), - length: items.length, - pageSize: $pageSize, - currentPage: getPageForIndex($index, $pageSize), - totalPages: getTotalPages($pageSize, items), - }; - }); + const setActiveRowIndex = (nextIndex: number | undefined = undefined) => { + if (nextIndex === undefined) activeRowIndex.set(nextIndex); + const pageItemSize = items.slice( + get(index), + get(index) + get(pageSize), + ).length; + const maxRowIndex = getMaxRowIndex(pageItemSize, get(pageSize)); + if (nextIndex <= maxRowIndex) { + activeRowIndex.set(nextIndex); + } + }; + + const nextRow = () => { + const pageItemSize = items.slice( + get(index), + get(index) + get(pageSize), + ).length; + const maxRowIndex = getMaxRowIndex(pageItemSize, get(pageSize)); + if (get(activeRowIndex) === undefined) { + activeRowIndex.set(0); + } else if (get(activeRowIndex) < maxRowIndex) { + activeRowIndex.set(get(activeRowIndex) + 1); + } + }; + + const previousRow = () => { + const nextIndex = get(activeRowIndex) >= 1 ? get(activeRowIndex) - 1 : 0; + activeRowIndex.set(nextIndex); + }; + + const { subscribe } = derived( + [index, pageSize, activeRowIndex], + ([$index, $pageSize, $activeRowIndex]) => { + const totalPages = getTotalPages($pageSize, items); + const currentPage = getPageForIndex($index, $pageSize); + return { + items: items.slice($index, $index + $pageSize), + initialItem: items[0], + hasPrevious: !outOfBounds($index - $pageSize, items), + hasNext: !outOfBounds($index + $pageSize, items), + startingIndex: $index, + endingIndex: getIndex($index + $pageSize - 1, items), + length: items.length, + pageSize: $pageSize, + currentPage, + totalPages, + activeRowIndex: $activeRowIndex, + pageShortcuts: getPageShortcuts(currentPage, totalPages), + }; + }, + ); return { subscribe, @@ -176,8 +298,12 @@ export const pagination = ( previous, jumpToPage, jumpToIndex, + jumpToHashPage, findIndex, findPage, + nextRow, + previousRow, + setActiveRowIndex, }; }; diff --git a/src/lib/stores/persist-store.ts b/src/lib/stores/persist-store.ts index 6969ea014..2f41a777c 100644 --- a/src/lib/stores/persist-store.ts +++ b/src/lib/stores/persist-store.ts @@ -1,6 +1,8 @@ -import { browser } from '$app/env'; -import { writable } from 'svelte/store'; import type { Writable } from 'svelte/store'; +import { writable } from 'svelte/store'; + +import { BROWSER } from 'esm-env'; + import { isFunction } from '$lib/utilities/is-function'; import { parseWithBigInt, @@ -11,13 +13,13 @@ export function persistStore( name: string, initialValue: T | (() => T) | null = null, broadcastToAll = false, -): Pick, 'subscribe' | 'set'> { +): Pick, 'subscribe' | 'set' | 'update'> { let initialStoreValue = isFunction<() => T>(initialValue) ? initialValue() : initialValue; let broadcaster: null | BroadcastChannel; - if (browser) { + if (BROWSER) { try { if (window?.localStorage?.getItem(name)) { initialStoreValue = parseWithBigInt( @@ -29,9 +31,9 @@ export function persistStore( } } - const { subscribe, set } = writable(initialStoreValue); + const { subscribe, set, update } = writable(initialStoreValue); - if (browser && broadcastToAll) { + if (BROWSER && broadcastToAll) { try { broadcaster = new BroadcastChannel(`persist-store-${name}`); broadcaster?.addEventListener('message', (event) => { @@ -45,7 +47,7 @@ export function persistStore( return { subscribe, set: (x: T) => { - if (browser) { + if (BROWSER) { if (broadcaster) broadcaster.postMessage(x); if (x === null) { window?.localStorage?.removeItem(name); @@ -55,5 +57,18 @@ export function persistStore( } set(x); }, + update: (updater: (x: T) => T) => { + if (BROWSER) { + window?.localStorage?.removeItem(name); + update((previousValue) => { + const updatedValue = updater(previousValue); + window?.localStorage?.setItem( + name, + stringifyWithBigInt(updatedValue), + ); + return updatedValue; + }); + } + }, }; } diff --git a/src/lib/stores/persisted-search-parameter.ts b/src/lib/stores/persisted-search-parameter.ts index 0c44f4171..dba558ec9 100644 --- a/src/lib/stores/persisted-search-parameter.ts +++ b/src/lib/stores/persisted-search-parameter.ts @@ -1,6 +1,8 @@ -import { browser } from '$app/env'; +import { get, type Writable, writable } from 'svelte/store'; + +import { BROWSER } from 'esm-env'; + import { page } from '$app/stores'; -import { writable, get, Writable } from 'svelte/store'; type SearchParameterValue = string | number | boolean | null; @@ -25,7 +27,7 @@ const saveToSearchParameters = ( parameter: string, value: SearchParameterValue, ) => { - if (browser) { + if (BROWSER) { const url = new URL(window?.location?.href); url.searchParams.set(parameter, String(value)); @@ -37,7 +39,7 @@ const getFromLocalStorage = ( parameter: string, persist: boolean, ): string | undefined => { - if (browser && persist) { + if (BROWSER && persist) { const value = window?.localStorage?.getItem(parameter); if (value) return value; } @@ -48,7 +50,7 @@ const saveToLocalStorage = ( value: SearchParameterValue, persist: boolean, ) => { - if (browser && persist) { + if (BROWSER && persist) { window?.localStorage?.setItem(parameter, String(value)); } }; diff --git a/src/lib/stores/previous-events.ts b/src/lib/stores/previous-events.ts index b9f71c7eb..5399eebe4 100644 --- a/src/lib/stores/previous-events.ts +++ b/src/lib/stores/previous-events.ts @@ -1,4 +1,4 @@ -import { writable, Writable } from 'svelte/store'; +import { writable, type Writable } from 'svelte/store'; import type { FetchEventsParameters } from '$lib/services/events-service'; diff --git a/src/lib/stores/reset-workflows.ts b/src/lib/stores/reset-workflows.ts new file mode 100644 index 000000000..e1b53f8dc --- /dev/null +++ b/src/lib/stores/reset-workflows.ts @@ -0,0 +1,6 @@ +import { persistStore } from './persist-store'; + +export const resetWorkflows = persistStore>( + 'resetWorkflows', + {}, +); diff --git a/src/lib/stores/schedules.ts b/src/lib/stores/schedules.ts index 916fdd032..46bc4fc90 100644 --- a/src/lib/stores/schedules.ts +++ b/src/lib/stores/schedules.ts @@ -1,16 +1,32 @@ -import { goto } from '$app/navigation'; import { writable } from 'svelte/store'; -import type { Schedule } from '$types'; -import { routeForSchedule, routeForSchedules } from '$lib/utilities/route-for'; +import { goto } from '$app/navigation'; +import { translate } from '$lib/i18n/translate'; import { createSchedule, editSchedule } from '$lib/services/schedule-service'; -import { calendarToComment } from '$lib/utilities/schedule-comment-formatting'; +import { setSearchAttributes } from '$lib/services/workflow-service'; +import type { Schedule } from '$lib/types'; +import type { + DescribeFullSchedule, + ScheduleActionParameters, + ScheduleInterval, + SchedulePresetsParameters, + ScheduleSpecParameters, +} from '$lib/types/schedule'; +import { encodePayloads } from '$lib/utilities/encode-payload'; +import { stringifyWithBigInt } from '$lib/utilities/parse-with-big-int'; +import { routeForSchedule, routeForSchedules } from '$lib/utilities/route-for'; import { convertDaysAndMonths, timeToInterval, } from '$lib/utilities/schedule-data-formatting'; +type ScheduleParameterArgs = { + action: ScheduleActionParameters; + spec: Partial; + presets: SchedulePresetsParameters; +}; + // TODO: Post Beta, add support of additional fields. // "startTime": "2022-07-04T03:18:59.668Z", // "endTime": "2022-07-04T03:18:59.668Z", @@ -18,9 +34,17 @@ import { // "timezoneName": "string", // "timezoneData": "string" +const getSearchAttributes = ( + attrs: (typeof setSearchAttributes.arguments)[0], +) => { + return attrs.length === 0 + ? null + : { indexedFields: { ...setSearchAttributes(attrs) } }; +}; + const setBodySpec = ( body: DescribeFullSchedule, - spec: ScheduleSpecParameters, + spec: Partial, presets: SchedulePresetsParameters, ) => { const { hour, minute, second, phase, cronString } = spec; @@ -33,7 +57,10 @@ const setBodySpec = ( body.schedule.spec.interval = []; } else if (preset === 'interval') { const interval = timeToInterval(days, hour, minute, second); - body.schedule.spec.interval = [{ interval, phase: phase || '0s' }]; + // The Schedule IntervalSpec implements IIntervalSpec which encodes/decodes string to Interval + body.schedule.spec.interval = [ + { interval, phase: phase || '0s' }, + ] as ScheduleInterval[]; body.schedule.spec.cronString = []; body.schedule.spec.calendar = []; } else { @@ -42,15 +69,6 @@ const setBodySpec = ( daysOfMonth, daysOfWeek, }); - const comment = calendarToComment({ - preset, - month, - dayOfMonth, - dayOfWeek, - hour, - minute, - second, - }); body.schedule.spec.calendar = [ { year: '*', @@ -60,7 +78,6 @@ const setBodySpec = ( hour, minute, second, - comment, }, ]; body.schedule.spec.interval = []; @@ -72,10 +89,41 @@ export const submitCreateSchedule = async ({ action, spec, presets, -}: ScheduleParameters): Promise => { - const { namespace, name, workflowId, workflowType, taskQueue } = action; +}: ScheduleParameterArgs): Promise => { + const { + namespace, + name, + workflowId, + workflowType, + taskQueue, + input, + encoding, + messageType, + searchAttributes, + workflowSearchAttributes, + } = action; + + let payloads; + + if (input) { + try { + payloads = await encodePayloads({ input, encoding, messageType }); + } catch (e) { + error.set(`${translate('data-encoder.encode-error')}: ${e?.message}`); + return; + } + } + const body: DescribeFullSchedule = { - schedule_id: name, + schedule_id: name.trim(), + searchAttributes: + searchAttributes.length === 0 + ? null + : { + indexedFields: { + ...setSearchAttributes(searchAttributes), + }, + }, schedule: { spec: { calendar: [], @@ -87,6 +135,15 @@ export const submitCreateSchedule = async ({ workflowId: workflowId, workflowType: { name: workflowType }, taskQueue: { name: taskQueue }, + input: payloads ? { payloads } : null, + searchAttributes: + workflowSearchAttributes.length === 0 + ? null + : { + indexedFields: { + ...setSearchAttributes(workflowSearchAttributes), + }, + }, }, }, }, @@ -97,6 +154,7 @@ export const submitCreateSchedule = async ({ // Wait 2 seconds for create to get it on fetchAllSchedules loading.set(true); const { error: err } = await createSchedule({ + scheduleId: name, namespace, body, }); @@ -114,14 +172,38 @@ export const submitCreateSchedule = async ({ }; export const submitEditSchedule = async ( - { action, spec, presets }: ScheduleParameters, + { action, spec, presets }: ScheduleParameterArgs, schedule: Schedule, scheduleId: string, ): Promise => { - const { namespace, name, workflowId, workflowType, taskQueue } = action; - const { preset } = presets; + const { + namespace, + name, + workflowId, + workflowType, + taskQueue, + input, + encoding, + messageType, + searchAttributes, + workflowSearchAttributes, + } = action; + + let payloads; + if (input) { + try { + payloads = await encodePayloads({ input, encoding, messageType }); + } catch (e) { + error.set(`${translate('data-encoder.encode-error')}: ${e?.message}`); + return; + } + } + + const { preset } = presets; const body: DescribeFullSchedule = { + schedule_id: scheduleId, + searchAttributes: getSearchAttributes(searchAttributes), schedule: { ...schedule, action: { @@ -130,11 +212,30 @@ export const submitEditSchedule = async ( workflowId, workflowType: { name: workflowType }, taskQueue: { name: taskQueue }, + ...(input !== undefined && { input: payloads ? { payloads } : null }), + searchAttributes: getSearchAttributes(workflowSearchAttributes), }, }, }, }; + const fields = body.schedule.action.startWorkflow?.header?.fields; + if (fields && Object.keys(fields).length > 0) { + try { + const entries = Object.entries(fields); + for (const [key, value] of entries) { + const encodedValue = await encodePayloads({ + input: stringifyWithBigInt(value), + encoding: 'json/plain', + }); + fields[key] = encodedValue[0]; + } + } catch (e) { + error.set(`${translate('data-encoder.encode-error')}: ${e?.message}`); + return; + } + } + if (preset === 'existing') { body.schedule.spec = schedule.spec; } else { @@ -164,3 +265,4 @@ export const submitEditSchedule = async ( export const loading = writable(false); export const error = writable(''); +export const schedulesCount = writable('0'); diff --git a/src/lib/stores/search-attributes.test.ts b/src/lib/stores/search-attributes.test.ts new file mode 100644 index 000000000..998318d52 --- /dev/null +++ b/src/lib/stores/search-attributes.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, test } from 'vitest'; + +import { + allSearchAttributes, + isCustomSearchAttribute, + workflowIncludesSearchAttribute, +} from './search-attributes'; + +describe('search attributes store', () => { + describe('isCustomSearchAttribute', () => { + test('returns false when the key is not a custom search attribute', () => { + allSearchAttributes.set({ + systemAttributes: { WorkflowType: 'Keyword' }, + customAttributes: {}, + }); + expect(isCustomSearchAttribute('CustomBool')).toBe(false); + }); + + test('returns true when the key is a custom search attribute', () => { + allSearchAttributes.set({ + systemAttributes: { WorkflowType: 'Keyword' }, + customAttributes: { CustomBool: 'Bool' }, + }); + expect(isCustomSearchAttribute('CustomBool')).toBe(true); + }); + }); + + describe('workflowIncludesSearchAttribute', () => { + test('returns true when the search attribute is defined on the workflow', () => { + const mockWorkflow = { + name: 'Mock Workflow', + id: 'abc-123', + searchAttributes: { + indexedFields: { + CustomBool: true, + }, + }, + }; + expect(workflowIncludesSearchAttribute(mockWorkflow, 'CustomBool')).toBe( + true, + ); + }); + + test('returns false when the search attribute is not defined on the workflow', () => { + const mockWorkflow = { + name: 'Mock Workflow', + id: 'abc-123', + searchAttributes: { + indexedFields: { + CustomInt: true, + }, + }, + }; + expect(workflowIncludesSearchAttribute(mockWorkflow, 'CustomBool')).toBe( + false, + ); + }); + + test('returns false when searchAttributes are not defined on the workflow', () => { + const mockWorkflow = { + name: 'Mock Workflow', + id: 'abc-123', + }; + + expect(workflowIncludesSearchAttribute(mockWorkflow, 'CustomBool')).toBe( + false, + ); + }); + + test('returns false when indexedFields are not defined on the searchAttributes of the workflow', () => { + const mockWorkflow = { + name: 'Mock Workflow', + id: 'abc-123', + searchAttributes: {}, + }; + + expect(workflowIncludesSearchAttribute(mockWorkflow, 'CustomBool')).toBe( + false, + ); + }); + }); +}); diff --git a/src/lib/stores/search-attributes.ts b/src/lib/stores/search-attributes.ts index d40257430..b4ac8e28b 100644 --- a/src/lib/stores/search-attributes.ts +++ b/src/lib/stores/search-attributes.ts @@ -1,16 +1,154 @@ -import { writable, get } from 'svelte/store'; - -export const searchAttributes = writable(); - -export const searchAttributeOptions = () => { - const attributes = get(searchAttributes); - return attributes - ? Object.entries(attributes).map(([key, value]) => { - return { - label: key, - value: key, - type: value, - }; - }) - : []; +import { derived, get, type Readable, writable } from 'svelte/store'; + +import { + SEARCH_ATTRIBUTE_TYPE, + type SearchAttributes, + type SearchAttributeType, + type WorkflowExecution, +} from '$lib/types/workflows'; + +type SearchAttributesStore = { + customAttributes: SearchAttributes; + systemAttributes: SearchAttributes; +}; + +export const allSearchAttributes = writable({ + customAttributes: {}, + systemAttributes: {}, +}); + +export const searchAttributes: Readable = derived( + [allSearchAttributes], + ([$allSearchAttributes]) => ({ + ...$allSearchAttributes.customAttributes, + ...$allSearchAttributes.systemAttributes, + }), +); + +export const scheduleSearchAttributes: Readable = derived( + [allSearchAttributes], + ([$allSearchAttributes]) => ({ + ScheduleId: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + ...$allSearchAttributes.customAttributes, + }), +); + +export const internalSearchAttributes: Readable = derived( + [allSearchAttributes], + ([$allSearchAttributes]) => $allSearchAttributes.systemAttributes, +); + +export const customSearchAttributes: Readable = derived( + [allSearchAttributes], + ([$allSearchAttributes]) => { + return $allSearchAttributes.customAttributes; + }, +); + +export const customSearchAttributeOptions: Readable< + { + label: string; + value: string; + type: SearchAttributeType; + }[] +> = derived([customSearchAttributes], ([$customSearchAttributes]) => { + return Object.entries($customSearchAttributes).map(([key, value]) => ({ + label: key, + value: key, + type: value, + })); +}); + +export const isCustomSearchAttribute = (key: string) => { + const customSearchAttrs = get(customSearchAttributes); + return key in customSearchAttrs; +}; + +export const workflowIncludesSearchAttribute = ( + workflow: WorkflowExecution, + searchAttribute: string, +): boolean => { + return searchAttribute in (workflow?.searchAttributes?.indexedFields ?? {}); +}; + +export type SearchAttributeOption = { + label: string; + value: string; + type: SearchAttributeType; }; + +type BaseSearchAttributeInput = { + label: string; +}; + +type BoolSearchAttributeInput = BaseSearchAttributeInput & { + value: boolean; + type: typeof SEARCH_ATTRIBUTE_TYPE.BOOL; +}; + +type NumberSearchAttributeInput = BaseSearchAttributeInput & { + value: number; + type: typeof SEARCH_ATTRIBUTE_TYPE.INT | typeof SEARCH_ATTRIBUTE_TYPE.DOUBLE; +}; + +type StringSearchAttributeInput = BaseSearchAttributeInput & { + value: string; + type: + | typeof SEARCH_ATTRIBUTE_TYPE.TEXT + | typeof SEARCH_ATTRIBUTE_TYPE.KEYWORD + | typeof SEARCH_ATTRIBUTE_TYPE.DATETIME; +}; + +type ListSearchAttributeInput = BaseSearchAttributeInput & { + value: string[]; + type: typeof SEARCH_ATTRIBUTE_TYPE.KEYWORDLIST; +}; +export type SearchAttributeInput = + | { + label: null; + value: null; + type: typeof SEARCH_ATTRIBUTE_TYPE.UNSPECIFIED; + } + | BoolSearchAttributeInput + | NumberSearchAttributeInput + | StringSearchAttributeInput + | ListSearchAttributeInput; + +export const searchAttributeOptions: Readable = + derived([searchAttributes], ([$searchAttributes]) => { + return $searchAttributes + ? Object.entries($searchAttributes).map(([key, value]) => { + return { + label: key, + value: key, + type: value, + }; + }) + : []; + }); + +export const sortedSearchAttributeOptions: Readable = + derived([searchAttributeOptions], ([$searchAttributeOptions]) => { + const popularOptions = [ + 'ExecutionStatus', + 'WorkflowId', + 'WorkflowType', + 'RunId', + 'StartTime', + 'CloseTime', + ]; + return $searchAttributeOptions + .sort((a, b) => { + if (a.label < b.label) return -1; + if (a.label > b.label) return 1; + return 0; + }) + .sort((a, b) => { + const indexA = popularOptions.indexOf(a.value); + const indexB = popularOptions.indexOf(b.value); + + if (indexA < 0) return 1; + if (indexB < 0) return -1; + return indexA - indexB; + }); + }); diff --git a/src/lib/stores/settings.ts b/src/lib/stores/settings.ts index 5024a25ee..15242ad54 100644 --- a/src/lib/stores/settings.ts +++ b/src/lib/stores/settings.ts @@ -1,24 +1,5 @@ -import { writable } from 'svelte/store'; +import { derived } from 'svelte/store'; -export const settings = writable({ - auth: { - enabled: false, - options: null, - }, - baseUrl: '', - codec: { - endpoint: '', - passAccessToken: false, - }, - defaultNamespace: null, - disableWriteActions: false, - showTemporalSystemNamespace: false, - notifyOnNewVersion: false, - feedbackURL: '', - runtimeEnvironment: { - isCloud: false, - isLocal: true, - envOverride: true, - }, - version: '', -}); +import { page } from '$app/stores'; + +export const settings = derived([page], ([$page]) => $page.data.settings); diff --git a/src/lib/stores/show-data-encoder.ts b/src/lib/stores/show-data-encoder.ts deleted file mode 100644 index 7e8639f90..000000000 --- a/src/lib/stores/show-data-encoder.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { writable } from 'svelte/store'; - -export const showDataEncoderSettings = writable(false); diff --git a/src/lib/stores/task-queue-view.ts b/src/lib/stores/task-queue-view.ts new file mode 100644 index 000000000..137fb9387 --- /dev/null +++ b/src/lib/stores/task-queue-view.ts @@ -0,0 +1,8 @@ +import { persistStore } from '$lib/stores/persist-store'; +import type { TaskQueueView } from '$lib/types/events'; + +export const taskQueueView = persistStore( + 'taskQueueView', + 'workers', + true, +); diff --git a/src/lib/stores/time-format.test.ts b/src/lib/stores/time-format.test.ts new file mode 100644 index 000000000..ef7097b52 --- /dev/null +++ b/src/lib/stores/time-format.test.ts @@ -0,0 +1,68 @@ +import { get } from 'svelte/store'; + +import { describe, expect, test } from 'vitest'; + +import { + formatOffset, + getTimezone, + getUTCOffset, + relativeTime, + timeFormat, + type TimeFormat, + Timezones, +} from './time-format'; + +describe('time format store', () => { + test('should return UTC as the default timeFormat', () => { + expect(get(timeFormat)).toBe('UTC'); + }); + test('should return false as the default for relativeTime', () => { + expect(get(relativeTime)).toBe(false); + }); +}); + +describe('getTimezone', () => { + test('should return the first zone for the specified time format in the Timezones object', () => { + expect(getTimezone('Pacific Daylight Time')).toBe('America/Los_Angeles'); + expect(getTimezone('Greenwich Mean Time')).toBe('Africa/Abidjan'); + }); + + test('should return the local timezone if the time format is local', () => { + expect(getTimezone('local')).toBe('UTC'); + }); + + test('should return the time format if the time format does not exist in the Timezones object', () => { + expect(getTimezone('UTC')).toBe('UTC'); + }); +}); + +describe('formatOffset', () => { + test('should return a formatted offset for positive numbers', () => { + expect(formatOffset(0)).toBe('+00:00'); + expect(formatOffset(1)).toBe('+01:00'); + expect(formatOffset(9)).toBe('+09:00'); + expect(formatOffset(12)).toBe('+12:00'); + }); + + test('should return a formatted offset for negative numbers', () => { + expect(formatOffset(-1)).toBe('-01:00'); + expect(formatOffset(-9)).toBe('-09:00'); + expect(formatOffset(-12)).toBe('-12:00'); + }); +}); + +describe('getUTCOffset', () => { + test('should return a formatted UTC offset for all Timezone options', () => { + Object.entries(Timezones).forEach(([format, { offset }]) => { + expect(getUTCOffset(format as TimeFormat)).toBe(formatOffset(offset)); + }); + }); + + test('should return a formatted UTC offset for local', () => { + expect(getUTCOffset('local')).toBe('+00:00'); + }); + + test('should return a formatted UTC offset for a timezone', () => { + expect(getUTCOffset('America/Phoenix' as TimeFormat)).toBe('-07:00'); + }); +}); diff --git a/src/lib/stores/time-format.ts b/src/lib/stores/time-format.ts index 63b5c75af..ae25a73a8 100644 --- a/src/lib/stores/time-format.ts +++ b/src/lib/stores/time-format.ts @@ -1,9 +1,1210 @@ +import { startOfDay } from 'date-fns'; +import * as dateTz from 'date-fns-tz'; + import { persistStore } from '$lib/stores/persist-store'; +import { getLocalTimezone } from '$lib/utilities/format-date'; + +type TimeFormatTypes = 'relative' | 'absolute'; + +export const TIME_UNIT_OPTIONS = ['minutes', 'hours', 'days']; + +export const timeFormat = persistStore('timeFormat', 'UTC' as TimeFormat); +export const timeFormatType = persistStore( + 'timeFormatType', + 'relative' as TimeFormatTypes, +); + +export const relativeTime = persistStore('relativeTime', false); +export const relativeTimeDuration = persistStore('relativeTimeDuration', ''); +export const relativeTimeUnit = persistStore( + 'relativeTimeUnit', + TIME_UNIT_OPTIONS[0], +); -export const timeFormat = persistStore('timeFormat', 'UTC'); +export const startDate = persistStore('startDate', startOfDay(new Date())); +export const startHour = persistStore('startHour', ''); +export const startMinute = persistStore('startMinute', ''); +export const startSecond = persistStore('startSecond', ''); + +export const endDate = persistStore('endDate', startOfDay(new Date())); +export const endHour = persistStore('endHour', ''); +export const endMinute = persistStore('endMinute', ''); +export const endSecond = persistStore('endSecond', ''); + +export type TimeFormat = keyof typeof Timezones | 'UTC' | 'local'; + +type TimeFormatOption = { + label: string; + value: TimeFormat; + abbr?: string; + offset?: number; + zones?: string[]; +}; + +export type TimeFormatOptions = TimeFormatOption[]; + +// Use this snippet to generate the Timezones object +// import { enUS } from 'date-fns/locale'; +// import { utcToZonedTime, getTimezoneOffset, format } from 'date-fns-tz'; +// const generateTimezoneOptions = () => { +// const timeZones = Intl.supportedValuesOf('timeZone'); +// return timeZones.reduce((acc, timeZone) => { +// const zonedTime = utcToZonedTime(new Date(), timeZone); +// const zoneString = format(zonedTime, 'zzzz', { +// timeZone, +// locale: enUS, +// }); +// if (acc[zoneString]) { +// acc[zoneString].zones.push(timeZone); +// } else { +// const zoneAbbr = format(zonedTime, 'z', { +// timeZone, +// locale: enUS, +// }); +// const offset = Math.floor( +// (getTimezoneOffset(timeZone) / (1000 * 60 * 60)) % 24, +// ); +// acc[zoneString] = { +// abbr: zoneAbbr, +// offset, +// zones: [timeZone], +// }; +// } +// return acc; +// }, {}); +// }; + +export const Timezones = { + 'Greenwich Mean Time': { + abbr: 'GMT', + offset: 0, + zones: [ + 'Africa/Abidjan', + 'Africa/Accra', + 'Africa/Bamako', + 'Africa/Banjul', + 'Africa/Bissau', + 'Africa/Conakry', + 'Africa/Dakar', + 'Africa/Freetown', + 'Africa/Lome', + 'Africa/Monrovia', + 'Africa/Nouakchott', + 'Africa/Ouagadougou', + 'Africa/Sao_Tome', + 'America/Danmarkshavn', + 'Atlantic/Reykjavik', + 'Atlantic/St_Helena', + ], + }, + 'East Africa Time': { + abbr: 'GMT+3', + offset: 3, + zones: [ + 'Africa/Addis_Ababa', + 'Africa/Asmera', + 'Africa/Dar_es_Salaam', + 'Africa/Djibouti', + 'Africa/Kampala', + 'Africa/Mogadishu', + 'Africa/Nairobi', + 'Indian/Antananarivo', + 'Indian/Comoro', + 'Indian/Mayotte', + ], + }, + 'Central European Standard Time': { + abbr: 'GMT+1', + offset: 1, + zones: ['Africa/Algiers', 'Africa/Tunis'], + }, + 'West Africa Standard Time': { + abbr: 'GMT+1', + offset: 1, + zones: [ + 'Africa/Bangui', + 'Africa/Brazzaville', + 'Africa/Douala', + 'Africa/Kinshasa', + 'Africa/Lagos', + 'Africa/Libreville', + 'Africa/Luanda', + 'Africa/Malabo', + 'Africa/Ndjamena', + 'Africa/Niamey', + 'Africa/Porto-Novo', + ], + }, + 'Central Africa Time': { + abbr: 'GMT+2', + offset: 2, + zones: [ + 'Africa/Blantyre', + 'Africa/Bujumbura', + 'Africa/Gaborone', + 'Africa/Harare', + 'Africa/Juba', + 'Africa/Khartoum', + 'Africa/Kigali', + 'Africa/Lubumbashi', + 'Africa/Lusaka', + 'Africa/Maputo', + 'Africa/Windhoek', + ], + }, + 'Eastern European Summer Time': { + abbr: 'GMT+3', + offset: 3, + zones: [ + 'Africa/Cairo', + 'Asia/Beirut', + 'Asia/Gaza', + 'Asia/Hebron', + 'Asia/Nicosia', + 'Europe/Athens', + 'Europe/Bucharest', + 'Europe/Chisinau', + 'Europe/Helsinki', + 'Europe/Kiev', + 'Europe/Mariehamn', + 'Europe/Riga', + 'Europe/Sofia', + 'Europe/Tallinn', + 'Europe/Uzhgorod', + 'Europe/Vilnius', + 'Europe/Zaporozhye', + ], + }, + 'GMT+01:00': { + abbr: 'GMT+1', + offset: 1, + zones: [ + 'Africa/Casablanca', + 'Africa/El_Aaiun', + 'Europe/Guernsey', + 'Europe/Isle_of_Man', + 'Europe/Jersey', + ], + }, + 'Central European Summer Time': { + abbr: 'GMT+2', + offset: 2, + zones: [ + 'Africa/Ceuta', + 'Arctic/Longyearbyen', + 'Europe/Amsterdam', + 'Europe/Andorra', + 'Europe/Belgrade', + 'Europe/Berlin', + 'Europe/Bratislava', + 'Europe/Brussels', + 'Europe/Budapest', + 'Europe/Busingen', + 'Europe/Copenhagen', + 'Europe/Gibraltar', + 'Europe/Ljubljana', + 'Europe/Luxembourg', + 'Europe/Madrid', + 'Europe/Malta', + 'Europe/Monaco', + 'Europe/Oslo', + 'Europe/Paris', + 'Europe/Podgorica', + 'Europe/Prague', + 'Europe/Rome', + 'Europe/San_Marino', + 'Europe/Sarajevo', + 'Europe/Skopje', + 'Europe/Stockholm', + 'Europe/Tirane', + 'Europe/Vaduz', + 'Europe/Vatican', + 'Europe/Vienna', + 'Europe/Warsaw', + 'Europe/Zagreb', + 'Europe/Zurich', + ], + }, + 'South Africa Standard Time': { + abbr: 'GMT+2', + offset: 2, + zones: ['Africa/Johannesburg', 'Africa/Maseru', 'Africa/Mbabane'], + }, + 'Eastern European Standard Time': { + abbr: 'GMT+2', + offset: 2, + zones: ['Africa/Tripoli', 'Europe/Kaliningrad'], + }, + 'Hawaii-Aleutian Daylight Time': { + abbr: 'HADT', + offset: -9, + zones: ['America/Adak'], + }, + 'Alaska Daylight Time': { + abbr: 'AKDT', + offset: -8, + zones: [ + 'America/Anchorage', + 'America/Juneau', + 'America/Metlakatla', + 'America/Nome', + 'America/Sitka', + 'America/Yakutat', + ], + }, + 'Atlantic Standard Time': { + abbr: 'AST', + offset: -4, + zones: [ + 'America/Anguilla', + 'America/Antigua', + 'America/Aruba', + 'America/Barbados', + 'America/Blanc-Sablon', + 'America/Curacao', + 'America/Dominica', + 'America/Grenada', + 'America/Guadeloupe', + 'America/Kralendijk', + 'America/Lower_Princes', + 'America/Marigot', + 'America/Martinique', + 'America/Montserrat', + 'America/Port_of_Spain', + 'America/Puerto_Rico', + 'America/Santo_Domingo', + 'America/St_Barthelemy', + 'America/St_Kitts', + 'America/St_Lucia', + 'America/St_Thomas', + 'America/St_Vincent', + 'America/Tortola', + ], + }, + 'Brasilia Standard Time': { + abbr: 'GMT-3', + offset: -3, + zones: [ + 'America/Araguaina', + 'America/Bahia', + 'America/Belem', + 'America/Fortaleza', + 'America/Maceio', + 'America/Recife', + 'America/Santarem', + 'America/Sao_Paulo', + ], + }, + 'Argentina Standard Time': { + abbr: 'GMT-3', + offset: -3, + zones: [ + 'America/Argentina/La_Rioja', + 'America/Argentina/Rio_Gallegos', + 'America/Argentina/Salta', + 'America/Argentina/San_Juan', + 'America/Argentina/San_Luis', + 'America/Argentina/Tucuman', + 'America/Argentina/Ushuaia', + 'America/Buenos_Aires', + 'America/Catamarca', + 'America/Cordoba', + 'America/Jujuy', + 'America/Mendoza', + ], + }, + 'Paraguay Standard Time': { + abbr: 'GMT-4', + offset: -4, + zones: ['America/Asuncion'], + }, + 'Central Standard Time': { + abbr: 'CST', + offset: -6, + zones: [ + 'America/Bahia_Banderas', + 'America/Belize', + 'America/Chihuahua', + 'America/Costa_Rica', + 'America/El_Salvador', + 'America/Guatemala', + 'America/Managua', + 'America/Merida', + 'America/Mexico_City', + 'America/Monterrey', + 'America/Regina', + 'America/Swift_Current', + 'America/Tegucigalpa', + ], + }, + 'Amazon Standard Time': { + abbr: 'GMT-4', + offset: -4, + zones: [ + 'America/Boa_Vista', + 'America/Campo_Grande', + 'America/Cuiaba', + 'America/Manaus', + 'America/Porto_Velho', + ], + }, + 'Colombia Standard Time': { + abbr: 'GMT-5', + offset: -5, + zones: ['America/Bogota'], + }, + 'Mountain Daylight Time': { + abbr: 'MDT', + offset: -6, + zones: [ + 'America/Boise', + 'America/Cambridge_Bay', + 'America/Ciudad_Juarez', + 'America/Denver', + 'America/Edmonton', + 'America/Inuvik', + 'America/Yellowknife', + ], + }, + 'Eastern Standard Time': { + abbr: 'EST', + offset: -5, + zones: [ + 'America/Cancun', + 'America/Cayman', + 'America/Coral_Harbour', + 'America/Jamaica', + 'America/Panama', + ], + }, + 'Venezuela Time': { + abbr: 'GMT-4', + offset: -4, + zones: ['America/Caracas'], + }, + 'French Guiana Time': { + abbr: 'GMT-3', + offset: -3, + zones: ['America/Cayenne'], + }, + 'Central Daylight Time': { + abbr: 'CDT', + offset: -5, + zones: [ + 'America/Chicago', + 'America/Indiana/Knox', + 'America/Indiana/Tell_City', + 'America/Matamoros', + 'America/Menominee', + 'America/North_Dakota/Beulah', + 'America/North_Dakota/Center', + 'America/North_Dakota/New_Salem', + 'America/Ojinaga', + 'America/Rainy_River', + 'America/Rankin_Inlet', + 'America/Resolute', + 'America/Winnipeg', + ], + }, + 'Mountain Standard Time': { + abbr: 'MST', + offset: -7, + zones: [ + 'America/Creston', + 'America/Dawson_Creek', + 'America/Fort_Nelson', + 'America/Phoenix', + ], + }, + 'Yukon Time': { + abbr: 'GMT-7', + offset: -7, + zones: ['America/Dawson', 'America/Whitehorse'], + }, + 'Eastern Daylight Time': { + abbr: 'EDT', + offset: -4, + zones: [ + 'America/Detroit', + 'America/Grand_Turk', + 'America/Indiana/Marengo', + 'America/Indiana/Petersburg', + 'America/Indiana/Vevay', + 'America/Indiana/Vincennes', + 'America/Indiana/Winamac', + 'America/Indianapolis', + 'America/Iqaluit', + 'America/Kentucky/Monticello', + 'America/Louisville', + 'America/Nassau', + 'America/New_York', + 'America/Nipigon', + 'America/Pangnirtung', + 'America/Port-au-Prince', + 'America/Thunder_Bay', + 'America/Toronto', + ], + }, + 'Acre Standard Time': { + abbr: 'GMT-5', + offset: -5, + zones: ['America/Eirunepe', 'America/Rio_Branco'], + }, + 'Atlantic Daylight Time': { + abbr: 'ADT', + offset: -3, + zones: [ + 'America/Glace_Bay', + 'America/Goose_Bay', + 'America/Halifax', + 'America/Moncton', + 'America/Thule', + 'Atlantic/Bermuda', + ], + }, + 'West Greenland Summer Time': { + abbr: 'GMT-2', + offset: -2, + zones: ['America/Godthab'], + }, + 'Ecuador Time': { + abbr: 'GMT-5', + offset: -5, + zones: ['America/Guayaquil'], + }, + 'Guyana Time': { + abbr: 'GMT-4', + offset: -4, + zones: ['America/Guyana'], + }, + 'Cuba Daylight Time': { + abbr: 'GMT-4', + offset: -4, + zones: ['America/Havana'], + }, + 'Mexican Pacific Standard Time': { + abbr: 'GMT-7', + offset: -7, + zones: ['America/Hermosillo', 'America/Mazatlan'], + }, + 'Bolivia Time': { + abbr: 'GMT-4', + offset: -4, + zones: ['America/La_Paz'], + }, + 'Peru Standard Time': { + abbr: 'GMT-5', + offset: -5, + zones: ['America/Lima'], + }, + 'Pacific Daylight Time': { + abbr: 'PDT', + offset: -7, + zones: ['America/Los_Angeles', 'America/Tijuana', 'America/Vancouver'], + }, + 'St. Pierre & Miquelon Daylight Time': { + abbr: 'GMT-2', + offset: -2, + zones: ['America/Miquelon'], + }, + 'Uruguay Standard Time': { + abbr: 'GMT-3', + offset: -3, + zones: ['America/Montevideo'], + }, + 'GMT-04:00': { + abbr: 'GMT-4', + offset: -4, + zones: ['America/Montreal'], + }, + 'Fernando de Noronha Standard Time': { + abbr: 'GMT-2', + offset: -2, + zones: ['America/Noronha'], + }, + 'Suriname Time': { + abbr: 'GMT-3', + offset: -3, + zones: ['America/Paramaribo'], + }, + 'GMT-03:00': { + abbr: 'GMT-3', + offset: -3, + zones: ['America/Punta_Arenas', 'Antarctica/Palmer'], + }, + 'Northwest Mexico Daylight Time': { + abbr: 'GMT-7', + offset: -7, + zones: ['America/Santa_Isabel'], + }, + 'Chile Standard Time': { + abbr: 'GMT-4', + offset: -4, + zones: ['America/Santiago'], + }, + 'East Greenland Summer Time': { + abbr: 'GMT', + offset: 0, + zones: ['America/Scoresbysund'], + }, + 'Newfoundland Daylight Time': { + abbr: 'GMT-2:30', + offset: -3, + zones: ['America/St_Johns'], + }, + 'Casey Time': { + abbr: 'GMT+11', + offset: 11, + zones: ['Antarctica/Casey'], + }, + 'Davis Time': { + abbr: 'GMT+7', + offset: 7, + zones: ['Antarctica/Davis'], + }, + 'Dumont-d’Urville Time': { + abbr: 'GMT+10', + offset: 10, + zones: ['Antarctica/DumontDUrville'], + }, + 'Australian Eastern Standard Time': { + abbr: 'GMT+10', + offset: 10, + zones: [ + 'Antarctica/Macquarie', + 'Australia/Brisbane', + 'Australia/Currie', + 'Australia/Hobart', + 'Australia/Lindeman', + 'Australia/Melbourne', + 'Australia/Sydney', + ], + }, + 'Mawson Time': { + abbr: 'GMT+5', + offset: 5, + zones: ['Antarctica/Mawson'], + }, + 'New Zealand Standard Time': { + abbr: 'GMT+12', + offset: 12, + zones: ['Antarctica/McMurdo', 'Pacific/Auckland'], + }, + 'Rothera Time': { + abbr: 'GMT-3', + offset: -3, + zones: ['Antarctica/Rothera'], + }, + 'Syowa Time': { + abbr: 'GMT+3', + offset: 3, + zones: ['Antarctica/Syowa'], + }, + 'GMT+02:00': { + abbr: 'GMT+2', + offset: 2, + zones: ['Antarctica/Troll'], + }, + 'Vostok Time': { + abbr: 'GMT+6', + offset: 6, + zones: ['Antarctica/Vostok'], + }, + 'Arabian Standard Time': { + abbr: 'GMT+3', + offset: 3, + zones: [ + 'Asia/Aden', + 'Asia/Baghdad', + 'Asia/Bahrain', + 'Asia/Kuwait', + 'Asia/Qatar', + 'Asia/Riyadh', + ], + }, + 'East Kazakhstan Time': { + abbr: 'GMT+6', + offset: 6, + zones: ['Asia/Almaty', 'Asia/Qostanay'], + }, + 'GMT+03:00': { + abbr: 'GMT+3', + offset: 3, + zones: [ + 'Asia/Amman', + 'Asia/Damascus', + 'Asia/Famagusta', + 'Europe/Istanbul', + 'Europe/Kirov', + ], + }, + 'Anadyr Standard Time': { + abbr: 'GMT+12', + offset: 12, + zones: ['Asia/Anadyr'], + }, + 'West Kazakhstan Time': { + abbr: 'GMT+5', + offset: 5, + zones: [ + 'Asia/Aqtau', + 'Asia/Aqtobe', + 'Asia/Atyrau', + 'Asia/Oral', + 'Asia/Qyzylorda', + ], + }, + 'Turkmenistan Standard Time': { + abbr: 'GMT+5', + offset: 5, + zones: ['Asia/Ashgabat'], + }, + 'Azerbaijan Standard Time': { + abbr: 'GMT+4', + offset: 4, + zones: ['Asia/Baku'], + }, + 'Indochina Time': { + abbr: 'GMT+7', + offset: 7, + zones: ['Asia/Bangkok', 'Asia/Phnom_Penh', 'Asia/Saigon', 'Asia/Vientiane'], + }, + 'GMT+07:00': { + abbr: 'GMT+7', + offset: 7, + zones: ['Asia/Barnaul', 'Asia/Tomsk'], + }, + 'Kyrgyzstan Time': { + abbr: 'GMT+6', + offset: 6, + zones: ['Asia/Bishkek'], + }, + 'Brunei Darussalam Time': { + abbr: 'GMT+8', + offset: 8, + zones: ['Asia/Brunei'], + }, + 'India Standard Time': { + abbr: 'GMT+5:30', + offset: 5, + zones: ['Asia/Calcutta', 'Asia/Colombo'], + }, + 'Yakutsk Standard Time': { + abbr: 'GMT+9', + offset: 9, + zones: ['Asia/Chita', 'Asia/Khandyga', 'Asia/Yakutsk'], + }, + 'Ulaanbaatar Standard Time': { + abbr: 'GMT+8', + offset: 8, + zones: ['Asia/Choibalsan', 'Asia/Ulaanbaatar'], + }, + 'Bangladesh Standard Time': { + abbr: 'GMT+6', + offset: 6, + zones: ['Asia/Dhaka'], + }, + 'East Timor Time': { + abbr: 'GMT+9', + offset: 9, + zones: ['Asia/Dili'], + }, + 'Gulf Standard Time': { + abbr: 'GMT+4', + offset: 4, + zones: ['Asia/Dubai', 'Asia/Muscat'], + }, + 'Tajikistan Time': { + abbr: 'GMT+5', + offset: 5, + zones: ['Asia/Dushanbe'], + }, + 'Hong Kong Standard Time': { + abbr: 'GMT+8', + offset: 8, + zones: ['Asia/Hong_Kong'], + }, + 'Hovd Standard Time': { + abbr: 'GMT+7', + offset: 7, + zones: ['Asia/Hovd'], + }, + 'Irkutsk Standard Time': { + abbr: 'GMT+8', + offset: 8, + zones: ['Asia/Irkutsk'], + }, + 'Western Indonesia Time': { + abbr: 'GMT+7', + offset: 7, + zones: ['Asia/Jakarta', 'Asia/Pontianak'], + }, + 'Eastern Indonesia Time': { + abbr: 'GMT+9', + offset: 9, + zones: ['Asia/Jayapura'], + }, + 'Israel Daylight Time': { + abbr: 'GMT+3', + offset: 3, + zones: ['Asia/Jerusalem'], + }, + 'Afghanistan Time': { + abbr: 'GMT+4:30', + offset: 4, + zones: ['Asia/Kabul'], + }, + 'Petropavlovsk-Kamchatski Standard Time': { + abbr: 'GMT+12', + offset: 12, + zones: ['Asia/Kamchatka'], + }, + 'Pakistan Standard Time': { + abbr: 'GMT+5', + offset: 5, + zones: ['Asia/Karachi'], + }, + 'Nepal Time': { + abbr: 'GMT+5:45', + offset: 5, + zones: ['Asia/Katmandu'], + }, + 'Krasnoyarsk Standard Time': { + abbr: 'GMT+7', + offset: 7, + zones: ['Asia/Krasnoyarsk', 'Asia/Novokuznetsk'], + }, + 'Malaysia Time': { + abbr: 'GMT+8', + offset: 8, + zones: ['Asia/Kuala_Lumpur', 'Asia/Kuching'], + }, + 'China Standard Time': { + abbr: 'GMT+8', + offset: 8, + zones: ['Asia/Macau', 'Asia/Shanghai'], + }, + 'Magadan Standard Time': { + abbr: 'GMT+11', + offset: 11, + zones: ['Asia/Magadan'], + }, + 'Central Indonesia Time': { + abbr: 'GMT+8', + offset: 8, + zones: ['Asia/Makassar'], + }, + 'Philippine Standard Time': { + abbr: 'GMT+8', + offset: 8, + zones: ['Asia/Manila'], + }, + 'Novosibirsk Standard Time': { + abbr: 'GMT+7', + offset: 7, + zones: ['Asia/Novosibirsk'], + }, + 'Omsk Standard Time': { + abbr: 'GMT+6', + offset: 6, + zones: ['Asia/Omsk'], + }, + 'Korean Standard Time': { + abbr: 'GMT+9', + offset: 9, + zones: ['Asia/Pyongyang', 'Asia/Seoul'], + }, + 'Myanmar Time': { + abbr: 'GMT+6:30', + offset: 6, + zones: ['Asia/Rangoon'], + }, + 'Sakhalin Standard Time': { + abbr: 'GMT+11', + offset: 11, + zones: ['Asia/Sakhalin'], + }, + 'Uzbekistan Standard Time': { + abbr: 'GMT+5', + offset: 5, + zones: ['Asia/Samarkand', 'Asia/Tashkent'], + }, + 'Singapore Standard Time': { + abbr: 'GMT+8', + offset: 8, + zones: ['Asia/Singapore'], + }, + 'GMT+11:00': { + abbr: 'GMT+11', + offset: 11, + zones: ['Asia/Srednekolymsk', 'Pacific/Bougainville'], + }, + 'Taipei Standard Time': { + abbr: 'GMT+8', + offset: 8, + zones: ['Asia/Taipei'], + }, + 'Georgia Standard Time': { + abbr: 'GMT+4', + offset: 4, + zones: ['Asia/Tbilisi'], + }, + 'Iran Standard Time': { + abbr: 'GMT+3:30', + offset: 3, + zones: ['Asia/Tehran'], + }, + 'Bhutan Time': { + abbr: 'GMT+6', + offset: 6, + zones: ['Asia/Thimphu'], + }, + 'Japan Standard Time': { + abbr: 'GMT+9', + offset: 9, + zones: ['Asia/Tokyo'], + }, + 'GMT+06:00': { + abbr: 'GMT+6', + offset: 6, + zones: ['Asia/Urumqi'], + }, + 'Vladivostok Standard Time': { + abbr: 'GMT+10', + offset: 10, + zones: ['Asia/Ust-Nera', 'Asia/Vladivostok'], + }, + 'Yekaterinburg Standard Time': { + abbr: 'GMT+5', + offset: 5, + zones: ['Asia/Yekaterinburg'], + }, + 'Armenia Standard Time': { + abbr: 'GMT+4', + offset: 4, + zones: ['Asia/Yerevan'], + }, + 'Azores Summer Time': { + abbr: 'GMT', + offset: 0, + zones: ['Atlantic/Azores'], + }, + 'Western European Summer Time': { + abbr: 'GMT+1', + offset: 1, + zones: [ + 'Atlantic/Canary', + 'Atlantic/Faeroe', + 'Atlantic/Madeira', + 'Europe/Lisbon', + ], + }, + 'Cape Verde Standard Time': { + abbr: 'GMT-1', + offset: -1, + zones: ['Atlantic/Cape_Verde'], + }, + 'South Georgia Time': { + abbr: 'GMT-2', + offset: -2, + zones: ['Atlantic/South_Georgia'], + }, + 'Falkland Islands Standard Time': { + abbr: 'GMT-3', + offset: -3, + zones: ['Atlantic/Stanley'], + }, + 'Australian Central Standard Time': { + abbr: 'GMT+9:30', + offset: 9, + zones: ['Australia/Adelaide', 'Australia/Broken_Hill', 'Australia/Darwin'], + }, + 'Australian Central Western Standard Time': { + abbr: 'GMT+8:45', + offset: 8, + zones: ['Australia/Eucla'], + }, + 'Lord Howe Standard Time': { + abbr: 'GMT+10:30', + offset: 10, + zones: ['Australia/Lord_Howe'], + }, + 'Australian Western Standard Time': { + abbr: 'GMT+8', + offset: 8, + zones: ['Australia/Perth'], + }, + 'GMT+04:00': { + abbr: 'GMT+4', + offset: 4, + zones: ['Europe/Astrakhan', 'Europe/Saratov', 'Europe/Ulyanovsk'], + }, + 'Irish Standard Time': { + abbr: 'GMT+1', + offset: 1, + zones: ['Europe/Dublin'], + }, + 'British Summer Time': { + abbr: 'GMT+1', + offset: 1, + zones: ['Europe/London'], + }, + 'Moscow Standard Time': { + abbr: 'GMT+3', + offset: 3, + zones: ['Europe/Minsk', 'Europe/Moscow', 'Europe/Simferopol'], + }, + 'Samara Standard Time': { + abbr: 'GMT+4', + offset: 4, + zones: ['Europe/Samara'], + }, + 'Volgograd Standard Time': { + abbr: 'GMT+3', + offset: 3, + zones: ['Europe/Volgograd'], + }, + 'Indian Ocean Time': { + abbr: 'GMT+6', + offset: 6, + zones: ['Indian/Chagos'], + }, + 'Christmas Island Time': { + abbr: 'GMT+7', + offset: 7, + zones: ['Indian/Christmas'], + }, + 'Cocos Islands Time': { + abbr: 'GMT+6:30', + offset: 6, + zones: ['Indian/Cocos'], + }, + 'French Southern & Antarctic Time': { + abbr: 'GMT+5', + offset: 5, + zones: ['Indian/Kerguelen'], + }, + 'Seychelles Time': { + abbr: 'GMT+4', + offset: 4, + zones: ['Indian/Mahe'], + }, + 'Maldives Time': { + abbr: 'GMT+5', + offset: 5, + zones: ['Indian/Maldives'], + }, + 'Mauritius Standard Time': { + abbr: 'GMT+4', + offset: 4, + zones: ['Indian/Mauritius'], + }, + 'Réunion Time': { + abbr: 'GMT+4', + offset: 4, + zones: ['Indian/Reunion'], + }, + 'Apia Standard Time': { + abbr: 'GMT+13', + offset: 13, + zones: ['Pacific/Apia'], + }, + 'Chatham Standard Time': { + abbr: 'GMT+12:45', + offset: 12, + zones: ['Pacific/Chatham'], + }, + 'Easter Island Standard Time': { + abbr: 'GMT-6', + offset: -6, + zones: ['Pacific/Easter'], + }, + 'Vanuatu Standard Time': { + abbr: 'GMT+11', + offset: 11, + zones: ['Pacific/Efate'], + }, + 'Phoenix Islands Time': { + abbr: 'GMT+13', + offset: 13, + zones: ['Pacific/Enderbury'], + }, + 'Tokelau Time': { + abbr: 'GMT+13', + offset: 13, + zones: ['Pacific/Fakaofo'], + }, + 'Fiji Standard Time': { + abbr: 'GMT+12', + offset: 12, + zones: ['Pacific/Fiji'], + }, + 'Tuvalu Time': { + abbr: 'GMT+12', + offset: 12, + zones: ['Pacific/Funafuti'], + }, + 'Galapagos Time': { + abbr: 'GMT-6', + offset: -6, + zones: ['Pacific/Galapagos'], + }, + 'Gambier Time': { + abbr: 'GMT-9', + offset: -9, + zones: ['Pacific/Gambier'], + }, + 'Solomon Islands Time': { + abbr: 'GMT+11', + offset: 11, + zones: ['Pacific/Guadalcanal'], + }, + 'Chamorro Standard Time': { + abbr: 'GMT+10', + offset: 10, + zones: ['Pacific/Guam', 'Pacific/Saipan'], + }, + 'Hawaii-Aleutian Standard Time': { + abbr: 'HST', + offset: -10, + zones: ['Pacific/Honolulu', 'Pacific/Johnston'], + }, + 'Line Islands Time': { + abbr: 'GMT+14', + offset: 14, + zones: ['Pacific/Kiritimati'], + }, + 'Kosrae Time': { + abbr: 'GMT+11', + offset: 11, + zones: ['Pacific/Kosrae'], + }, + 'Marshall Islands Time': { + abbr: 'GMT+12', + offset: 12, + zones: ['Pacific/Kwajalein', 'Pacific/Majuro'], + }, + 'Marquesas Time': { + abbr: 'GMT-9:30', + offset: -10, + zones: ['Pacific/Marquesas'], + }, + 'Samoa Standard Time': { + abbr: 'GMT-11', + offset: -11, + zones: ['Pacific/Midway', 'Pacific/Pago_Pago'], + }, + 'Nauru Time': { + abbr: 'GMT+12', + offset: 12, + zones: ['Pacific/Nauru'], + }, + 'Niue Time': { + abbr: 'GMT-11', + offset: -11, + zones: ['Pacific/Niue'], + }, + 'Norfolk Island Standard Time': { + abbr: 'GMT+11', + offset: 11, + zones: ['Pacific/Norfolk'], + }, + 'New Caledonia Standard Time': { + abbr: 'GMT+11', + offset: 11, + zones: ['Pacific/Noumea'], + }, + 'Palau Time': { + abbr: 'GMT+9', + offset: 9, + zones: ['Pacific/Palau'], + }, + 'Pitcairn Time': { + abbr: 'GMT-8', + offset: -8, + zones: ['Pacific/Pitcairn'], + }, + 'Ponape Time': { + abbr: 'GMT+11', + offset: 11, + zones: ['Pacific/Ponape'], + }, + 'Papua New Guinea Time': { + abbr: 'GMT+10', + offset: 10, + zones: ['Pacific/Port_Moresby'], + }, + 'Cook Islands Standard Time': { + abbr: 'GMT-10', + offset: -10, + zones: ['Pacific/Rarotonga'], + }, + 'Tahiti Time': { + abbr: 'GMT-10', + offset: -10, + zones: ['Pacific/Tahiti'], + }, + 'Gilbert Islands Time': { + abbr: 'GMT+12', + offset: 12, + zones: ['Pacific/Tarawa'], + }, + 'Tonga Standard Time': { + abbr: 'GMT+13', + offset: 13, + zones: ['Pacific/Tongatapu'], + }, + 'Chuuk Time': { + abbr: 'GMT+10', + offset: 10, + zones: ['Pacific/Truk'], + }, + 'Wake Island Time': { + abbr: 'GMT+12', + offset: 12, + zones: ['Pacific/Wake'], + }, + 'Wallis & Futuna Time': { + abbr: 'GMT+12', + offset: 12, + zones: ['Pacific/Wallis'], + }, +} as const; + +export const TimezoneOptions: TimeFormatOptions = Object.entries(Timezones) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .map(([key, value]: [TimeFormat, any]) => ({ + label: key, + value: key, + ...value, + })) + .sort((a, b) => { + if (a.label > b.label) return 1; + if (b.label > a.label) return -1; + return 0; + }); + +export const getTimezone = (timeFormat: TimeFormat): string => { + if (timeFormat === 'local') return getLocalTimezone(); + return Timezones[timeFormat]?.zones[0] ?? timeFormat; +}; + +export const formatOffset = (offset: number) => { + const formattedOffset = String(Math.abs(offset)).padStart(2, '0'); + return offset >= 0 ? `+${formattedOffset}:00` : `-${formattedOffset}:00`; +}; -export type TimeFormatOptions = { label: string; option: TimeFormat }[]; +export const getUTCOffset = (timeFormat: TimeFormat): string => { + let offset: number | undefined = Timezones[timeFormat]?.offset; -export const setTimeFormat = (format: TimeFormat): void => { - timeFormat.set(format); + if (offset === undefined) { + const timezone = getTimezone(timeFormat); + const offsetInMilliseconds = dateTz.getTimezoneOffset(timezone); + if (offsetInMilliseconds) { + offset = offsetInMilliseconds / 1000 / 60 / 60; + } else { + offset = 0; + } + } + return formatOffset(offset); }; diff --git a/src/lib/stores/toaster.test.ts b/src/lib/stores/toaster.test.ts new file mode 100644 index 000000000..b657bad25 --- /dev/null +++ b/src/lib/stores/toaster.test.ts @@ -0,0 +1,72 @@ +import { get } from 'svelte/store'; + +import { afterEach, describe, expect, it } from 'vitest'; + +import { toaster } from './toaster'; + +describe('toaster', () => { + afterEach(() => { + toaster.clear(); + }); + + it('should have a subscribe function', () => { + expect(toaster.subscribe).toBeDefined(); + }); + + it('should start with an empty array', () => { + expect(get(toaster)).toEqual([]); + }); + + describe('add', () => { + it('should increase the length by 1', () => { + toaster.push({ variant: 'error', message: 'This is an error' }); + expect(get(toaster)).toHaveLength(1); + }); + + it('should have the correct data', () => { + toaster.push({ variant: 'error', message: 'This is an error' }); + const [toast] = get(toaster); + + expect(toast.variant).toBe('error'); + expect(toast.message).toBe('This is an error'); + }); + }); + + describe('clear', () => { + it('should clear out all of the notifications', () => { + toaster.push({ variant: 'error', message: 'This is an error' }); + toaster.push({ variant: 'error', message: 'This is an error' }); + toaster.push({ variant: 'error', message: 'This is an error' }); + + toaster.clear(); + + expect(get(toaster)).toHaveLength(0); + }); + }); + + describe('dismiss', () => { + it('should remove a notification', () => { + toaster.push({ variant: 'error', message: 'This is an error' }); + toaster.push({ variant: 'success', message: 'Everything went well' }); + + const [toast] = get(toaster); + const { id } = toast; + + toaster.pop(id); + + expect(get(toaster)).toHaveLength(1); + }); + + it('should remove a the correct notification', () => { + toaster.push({ variant: 'error', message: 'This is an error' }); + toaster.push({ variant: 'success', message: 'Everything went well' }); + + const [toast] = get(toaster); + const { id } = toast; + + toaster.pop(id); + + expect(get(toaster)).not.toContain(toast); + }); + }); +}); diff --git a/src/lib/stores/toaster.ts b/src/lib/stores/toaster.ts new file mode 100644 index 000000000..c26bcc399 --- /dev/null +++ b/src/lib/stores/toaster.ts @@ -0,0 +1,57 @@ +import { get, writable, type Writable } from 'svelte/store'; + +import { v4 } from 'uuid'; + +import type { Toast, ToastPosition } from '$lib/types/holocene'; + +const toasts = writable([]); +const toastPosition = writable('bottom-right'); +export interface Toaster extends Writable { + push: (toast: Toast) => void; + pop: (id: string) => void; + clear: () => void; + toasts: Writable; + setPosition: (newPosition: ToastPosition) => void; + position: Writable; +} + +const setPosition = (position: ToastPosition): void => { + toastPosition.set(position); +}; + +const push = (toast: Toast) => { + const toastWithDefaults: Toast = { + id: v4(), + duration: 3000, + variant: 'primary', + ...toast, + }; + toasts.update((ts) => [...ts, toastWithDefaults]); + const timeoutId = setTimeout(() => { + pop(toastWithDefaults.id); + if (get(toasts).length === 0) { + setPosition('bottom-right'); + } + clearTimeout(timeoutId); + }, toastWithDefaults.duration); +}; + +const pop = (id: string) => { + toasts.update((ts) => ts.filter((t) => t.id !== id)); +}; + +const clear = (): void => { + toasts.set([]); +}; + +export const toaster: Toaster = { + push, + pop, + clear, + toasts, + set: toasts.set, + subscribe: toasts.subscribe, + update: toasts.update, + setPosition, + position: toastPosition, +}; diff --git a/src/lib/stores/versions.ts b/src/lib/stores/versions.ts index 3de7c90df..25a00847b 100644 --- a/src/lib/stores/versions.ts +++ b/src/lib/stores/versions.ts @@ -1,4 +1,5 @@ import { derived } from 'svelte/store'; + import { cluster } from './cluster'; import { settings } from './settings'; diff --git a/src/lib/stores/workflow-run.ts b/src/lib/stores/workflow-run.ts index 4c7ef3d3d..9f1bf6a7e 100644 --- a/src/lib/stores/workflow-run.ts +++ b/src/lib/stores/workflow-run.ts @@ -1,80 +1,39 @@ -import { derived, readable, writable } from 'svelte/store'; -import { page } from '$app/stores'; +import { writable } from 'svelte/store'; -import { withLoading } from '$lib/utilities/stores/with-loading'; -import type { StartStopNotifier } from 'svelte/store'; -import { fetchWorkflow } from '$lib/services/workflow-service'; -import { getPollers } from '$lib/services/pollers-service'; import type { GetPollersResponse } from '$lib/services/pollers-service'; -import { decodeURIForSvelte } from '$lib/utilities/encode-uri'; -import { toDecodedPendingActivities } from '$lib/models/pending-activities'; -import { authUser } from '$lib/stores/auth-user'; +import { persistStore } from '$lib/stores/persist-store'; +import type { WorkflowExecution, WorkflowMetadata } from '$lib/types/workflows'; export const refresh = writable(0); -const namespace = derived([page], ([$page]) => $page.params.namespace); -const workflowId = derived([page], ([$page]) => $page.params.workflow); -const runId = derived([page], ([$page]) => $page.params.run); -const settings = derived([page], ([$page]) => $page.stuff.settings); -const accessToken = derived( - [authUser], - ([$authUser]) => $authUser?.accessToken, -); -const parameters = derived( - [namespace, workflowId, runId, settings, accessToken, refresh], - ([$namespace, $workflowId, $runId, $settings, $accessToken, $refresh]) => { - return { - namespace: $namespace, - workflowId: decodeURIForSvelte($workflowId ?? ''), - runId: $runId, - settings: $settings, - accessToken: $accessToken, - refresh: $refresh, - }; - }, -); - -type WorkflowRunStore = { +export type WorkflowRunWithWorkers = { workflow: WorkflowExecution | null; - workers: GetPollersResponse | null; -}; -const initialWorkflowRun: WorkflowRunStore = { workflow: null, workers: null }; - -const updateWorkflowRun: StartStopNotifier<{ - workflow: WorkflowExecution; workers: GetPollersResponse; -}> = (set) => { - return parameters.subscribe( - ({ namespace, workflowId, runId, settings, accessToken }) => { - if (namespace && workflowId && runId) { - withLoading(loading, updating, async () => { - const workflow = await fetchWorkflow({ - namespace, - workflowId, - runId, - }); - const { taskQueue } = workflow; - const workers = await getPollers({ queue: taskQueue, namespace }); - workflow.pendingActivities = await toDecodedPendingActivities( - workflow, - namespace, - settings, - accessToken, - ); + metadata: WorkflowMetadata; + userMetadata: { + summary: string; + details: string; + }; +}; - set({ workflow, workers }); - }); - } else { - loading.set(true); - updating.set(false); - } - }, - ); +export const initialWorkflowRun: WorkflowRunWithWorkers = { + workflow: null, + workers: { pollers: [], taskQueueStatus: null }, + metadata: undefined, + userMetadata: { + summary: '', + details: '', + }, }; -export const updating = writable(true); -export const loading = writable(true); -export const workflowRun = readable( - initialWorkflowRun, - updateWorkflowRun, +export const workflowRun = writable(initialWorkflowRun); +export const workflowSummaryViewOpen = persistStore( + 'workflowSummaryView', + true, + true, +); +export const workflowTimelineViewOpen = persistStore( + 'workflowTimelineView', + true, + true, ); diff --git a/src/lib/stores/workflows.ts b/src/lib/stores/workflows.ts index 1e1422ad5..e5343bc00 100644 --- a/src/lib/stores/workflows.ts +++ b/src/lib/stores/workflows.ts @@ -1,71 +1,51 @@ -import { derived, readable, writable } from 'svelte/store'; +import { derived, writable } from 'svelte/store'; + import { page } from '$app/stores'; -import { fetchAllWorkflows } from '$lib/services/workflow-service'; -import { withLoading } from '$lib/utilities/stores/with-loading'; +import type { FilterParameters } from '$lib/types/workflows'; +import { minimumVersionRequired } from '$lib/utilities/version-check'; -import type { StartStopNotifier } from 'svelte/store'; -import { publicPath } from '$lib/utilities/get-public-path'; +import { isCloud } from './advanced-visibility'; +import { hideChildWorkflows } from './filters'; +import { temporalVersion } from './versions'; export const refresh = writable(0); -const namespace = derived([page], ([$page]) => $page.params.namespace); -const query = derived([page], ([$page]) => $page.url.searchParams.get('query')); -const path = derived([page], ([$page]) => $page.url.pathname); - -const parameters = derived( - [namespace, query, path, refresh], - ([$namespace, $query, $path, $refresh]) => { - return { - namespace: $namespace, - query: $query, - path: $path, - refresh: $refresh, - }; - }, +export const hideWorkflowQueryErrors = derived( + [page], + ([$page]) => $page.data?.settings?.hideWorkflowQueryErrors, ); -const setCounts = (_workflowCount: { totalCount: number; count: number }) => { - workflowCount.set(_workflowCount); -}; - -const updateWorkflows: StartStopNotifier = (set) => { - return parameters.subscribe(({ namespace, query, path }) => { - const isWorkflowsPage = - path == `${publicPath}/namespaces/${namespace}/workflows`; +export const disableWorkflowCountsRefresh = derived( + [page], + ([$page]) => $page.data?.settings?.refreshWorkflowCountsDisabled, +); - if (isWorkflowsPage) { - withLoading(loading, updating, async () => { - const { workflows, error } = await fetchAllWorkflows(namespace, { - query, - }); - set(workflows); +export const canFetchChildWorkflows = derived( + [isCloud, temporalVersion], + ([$isCloud, $temporalVersion]) => { + return $isCloud || minimumVersionRequired('1.23.0', $temporalVersion); + }, +); - // Add back when 1.19 lands for count - // const workflowCount = await fetchWorkflowCount(namespace, query); - // setCounts(workflowCount); - if (error) { - workflowError.set(error); - } else { - workflowError.set(''); - } - }); +const query = derived([page], ([$page]) => $page.url.searchParams.get('query')); +export const queryWithParentWorkflowId = derived( + [query, canFetchChildWorkflows, hideChildWorkflows], + ([$query, $canFetchChildWorkflows, $hideChildWorkflows]) => { + if ($canFetchChildWorkflows && $hideChildWorkflows && !$query) { + return 'ParentWorkflowId is NULL'; } - }); -}; + return $query; + }, +); export type ParsedParameters = FilterParameters & { timeRange?: string }; -type WorkflowsSearch = { - parameters: ParsedParameters; - searchType: 'basic' | 'advanced'; -}; -export const workflowsSearch = writable({ - parameters: {}, - searchType: 'basic', -}); +export const workflowsSearchParams = writable(''); export const updating = writable(true); export const loading = writable(true); -export const workflowCount = writable({ count: 0, totalCount: 0 }); +export const workflowCount = writable({ + count: 0, + newCount: 0, +}); export const workflowError = writable(''); -export const workflows = readable([], updateWorkflows); export const workflowsQuery = writable(''); diff --git a/src/lib/svelte-mocks/app/env.ts b/src/lib/svelte-mocks/app/environment.ts similarity index 100% rename from src/lib/svelte-mocks/app/env.ts rename to src/lib/svelte-mocks/app/environment.ts diff --git a/src/lib/svelte-mocks/app/paths.ts b/src/lib/svelte-mocks/app/paths.ts new file mode 100644 index 000000000..2a06a0280 --- /dev/null +++ b/src/lib/svelte-mocks/app/paths.ts @@ -0,0 +1 @@ +export const base = ''; diff --git a/src/lib/svelte-mocks/app/stores.ts b/src/lib/svelte-mocks/app/stores.ts index c8b93833f..d77b3eb36 100644 --- a/src/lib/svelte-mocks/app/stores.ts +++ b/src/lib/svelte-mocks/app/stores.ts @@ -1,10 +1,12 @@ import { readable } from 'svelte/store'; +import type { Settings } from '$lib/types/global'; + interface Page = Record> { url: URL; params: Params; routeId: string | null; - stuff: App.Stuff; + data: App.PageData; status: number; error: Error | null; } @@ -15,13 +17,23 @@ const settings: Settings = { options: null, }, baseUrl: 'http://localhost:3000', + bannerText: '', codec: { endpoint: '', passAccessToken: false, + includeCredentials: false, }, defaultNamespace: 'default', disableWriteActions: false, showTemporalSystemNamespace: false, + batchActionsDisabled: false, + workflowResetDisabled: false, + workflowCancelDisabled: false, + workflowSignalDisabled: false, + workflowUpdateDisabled: false, + workflowTerminateDisabled: false, + hideWorkflowQueryErrors: false, + activityCommandsDisabled: false, notifyOnNewVersion: true, feedbackURL: '', runtimeEnvironment: { @@ -29,10 +41,10 @@ const settings: Settings = { isLocal: true, envOverride: true, }, - version: '2.0.0', + version: '2.28.0', }; -const stuff: App.Stuff = { +const data: App.PageData = { settings, }; @@ -43,7 +55,7 @@ export const page = readable({ }, routeId: 'namespaces/[namespace]/workflows@root', status: 200, - stuff, + data, url: new URL( 'http://localhost:3000/namespaces/default/workflows?search=basic&query=WorkflowType%3D%22testing%22', ), diff --git a/src/lib/theme/colors.ts b/src/lib/theme/colors.ts new file mode 100644 index 000000000..9f28408c0 --- /dev/null +++ b/src/lib/theme/colors.ts @@ -0,0 +1,161 @@ +export type Palette = typeof palette; +export type PaletteColor = keyof Palette; + +export const palette = { + blue: { + 50: '#EFF5FF', + 100: '#DBE8FE', + 200: '#BFD7FE', + 300: '#93BBFD', + 400: '#609AFA', + 500: '#3B82F6', + 600: '#2570EB', + 700: '#1D64D8', + 800: '#1E55AF', + 900: '#1E478A', + 950: '#0F172A', + DEFAULT: '#3B82F6', + }, + cyan: { + 50: '#ECFCFF', + 100: '#CFF7FE', + 200: '#A5EFFC', + 300: '#67E4F9', + 400: '#22D0EE', + 500: '#06B6D4', + 600: '#0899B2', + 700: '#0E7D90', + 800: '#156775', + 900: '#165863', + 950: '#083B44', + DEFAULT: '#06B6D4', + }, + green: { + 50: '#EDFFF6', + 200: '#AEFFD8', + 100: '#D5FFEB', + 300: '#70FFBC', + 400: '#2BFD98', + 500: '#00F37E', + 600: '#00C05F', + 700: '#00964E', + 800: '#067541', + 900: '#076037', + 950: '#00371D', + DEFAULT: '#00F37E', + }, + indigo: { + 50: '#EEF4FF', + 100: '#E0EAFF', + 200: '#C6D8FF', + 300: '#A4BCFD', + 400: '#8098F9', + 500: '#6173F3', + 600: '#444CE7', + 700: '#363BCC', + 800: '#2F34A4', + 900: '#2D3382', + 950: '#1A1C4C', + DEFAULT: '#6173F3', + }, + orange: { + 50: '#FFF4ED', + 100: '#FFE6D5', + 200: '#FECCAA', + 300: '#FDAC74', + 400: '#FB8A3C', + 500: '#F97316', + 600: '#EA670C', + 700: '#C2570C', + 800: '#9A4A12', + 900: '#7C3D12', + 950: '#432007', + DEFAULT: '#F97316', + }, + pink: { + 50: '#FFF2FF', + 100: '#FEE4FF', + 200: '#FEC7FF', + 300: '#FF9CFD', + 400: '#FF61FA', + 500: '#FF26FF', + 600: '#EA05F4', + 700: '#D300D8', + 800: '#A403A5', + 900: '#860985', + 950: '#5C005B', + DEFAULT: '#FF26FF', + }, + purple: { + 50: '#F7F3FF', + 100: '#EFE9FE', + 200: '#E2D6FE', + 300: '#CBB5FD', + 400: '#AD8BFA', + 500: '#8B5CF6', + 600: '#713AED', + 700: '#5E28D9', + 800: '#4E21B6', + 900: '#421D95', + 950: '#2A1065', + DEFAULT: '#8B5CF6', + }, + red: { + 50: '#FFF3ED', + 100: '#FFE4D4', + 200: '#FFC4A8', + 300: '#FF9B70', + 400: '#FF6637', + 500: '#FF4518', + 600: '#F02406', + 700: '#C71607', + 800: '#9E130E', + 900: '#7F140F', + 950: '#450505', + DEFAULT: '#FF4518', + }, + slate: { + 50: '#E8EFFF', + 100: '#C9D9F0', + 200: '#AEBED9', + 300: '#92A4C3', + 400: '#7C8FB1', + 500: '#667CA1', + 600: '#576E8F', + 700: '#465A78', + 800: '#273860', + 900: '#1E293B', + 950: '#0F172A', + DEFAULT: '#667CA1', + }, + yellow: { + 50: '#FFFBEB', + 100: '#FFF4C6', + 200: '#FEE989', + 300: '#FED64B', + 400: '#FEC321', + 500: '#F8A208', + 600: '#DC7A03', + 700: '#B65507', + 800: '#94420C', + 900: '#79360E', + 950: '#461B02', + DEFAULT: '#F8A208', + }, +} as const satisfies Record; + +export const colors = { + white: '#FFFFFF', + 'off-white': '#F8FAFC', + black: '#000000', + 'space-black': '#141414', + 'off-black': palette['slate'][950], + 'code-black': '#292D3E', + current: 'currentColor', + transparent: 'transparent', + mint: '#59FDA0', + ...palette, +} as const satisfies Record< + string, + HexColor | 'transparent' | 'currentColor' | Shades +>; diff --git a/src/lib/theme/plugin.ts b/src/lib/theme/plugin.ts new file mode 100644 index 000000000..2317770f4 --- /dev/null +++ b/src/lib/theme/plugin.ts @@ -0,0 +1,255 @@ +import plugin from 'tailwindcss/plugin'; + +import { colors } from './colors'; +import { css } from './utilities'; +import { dark, light } from './variables'; + +const textStyles = plugin(({ addBase, theme }) => { + addBase({ + h1: { + fontSize: theme('fontSize.3xl'), + fontWeight: theme('fontWeight.medium'), + }, + h2: { + fontSize: theme('fontSize.2xl'), + fontWeight: theme('fontWeight.medium'), + }, + h3: { + fontSize: theme('fontSize.xl'), + fontWeight: theme('fontWeight.medium'), + }, + h4: { + fontSize: theme('fontSize.lg'), + fontWeight: theme('fontWeight.medium'), + }, + h5: { + fontSize: theme('fontSize.base'), + fontWeight: theme('fontWeight.medium'), + }, + h6: { + fontSize: theme('fontSize.sm'), + fontWeight: theme('fontWeight.medium'), + }, + '.body-normal': { + fontSize: theme('fontSize.sm'), + fontWeight: theme('fontWeight.normal'), + }, + '.body-medium': { + fontSize: theme('fontSize.sm'), + fontWeight: theme('fontWeight.medium'), + }, + '.body-small': { + fontSize: theme('fontSize.xs'), + fontWeight: theme('fontWeight.normal'), + }, + '.body-small-medium': { + fontSize: theme('fontSize.xs'), + fontWeight: theme('fontWeight.medium'), + }, + '.body-small-mono': { + fontFamily: theme('fontFamily.mono'), + fontSize: theme('fontSize.xs'), + fontWeight: theme('fontWeight.normal'), + }, + }); +}); + +const temporal = plugin( + ({ addComponents, addBase }) => { + addBase({ + ':root': light, + '[data-theme="dark"]': dark, + }); + + addComponents({ + '.surface-background': { + backgroundColor: css('--color-surface-background'), + color: css('--color-text-primary'), + }, + '.surface-primary': { + backgroundColor: css('--color-surface-primary'), + color: css('--color-text-primary'), + }, + '.surface-secondary': { + backgroundColor: css('--color-surface-secondary'), + color: css('--color-text-primary'), + }, + '.surface-interactive': { + backgroundColor: css('--color-interactive-surface'), + color: css('--color-text-white'), + '&:focus-visible': { + backgroundColor: css('--color-interactive-hover'), + }, + '&:hover': { + backgroundColor: css('--color-interactive-hover'), + }, + '&:active': { + backgroundColor: css('--color-interactive-active'), + }, + }, + '.surface-interactive-secondary': { + backgroundColor: css('--color-interactive-secondary-surface'), + color: css('--color-text-primary'), + '&:focus-visible': { + backgroundColor: css('--color-interactive-secondary-hover'), + }, + '&:hover': { + backgroundColor: css('--color-interactive-secondary-hover'), + }, + '&:active': { + backgroundColor: css('--color-interactive-secondary-active'), + }, + }, + '.surface-interactive-danger': { + backgroundColor: css('--color-interactive-danger-surface'), + color: css('--color-text-black'), + '&:focus-visible': { + backgroundColor: css('--color-interactive-danger-hover'), + }, + '&:hover': { + backgroundColor: css('--color-interactive-danger-hover'), + }, + '&:active': { + backgroundColor: css('--color-interactive-danger-active'), + }, + }, + '.surface-interactive-ghost': { + backgroundColor: css('--color-surface-primary'), + color: css('--color-text-primary'), + '&:focus-visible': { + backgroundColor: css('--color-interactive-ghost-hover'), + }, + '&:hover': { + backgroundColor: css('--color-interactive-ghost-hover'), + }, + '&:active': { + backgroundColor: css('--color-interactive-ghost-active'), + }, + }, + '.surface-inverse': { + backgroundColor: css('--color-surface-inverse'), + color: css('--color-text-inverse'), + }, + '.surface-subtle': { + backgroundColor: css('--color-surface-subtle'), + color: css('--color-text-primary'), + }, + '.surface-table': { + backgroundColor: css('--color-surface-table'), + color: css('--color-text-inverse'), + }, + '.surface-table-header': { + backgroundColor: css('--color-surface-table-header'), + color: css('--color-text-primary'), + }, + '.surface-warning': { + backgroundColor: css('--color-surface-subtle'), + color: css('--color-text-black'), + }, + '.surface-danger': { + backgroundColor: css('--color-surface-danger'), + color: css('--color-text-primary'), + }, + '.surface-table-related-hover': { + backgroundColor: css('--color-surface-table-related-hover'), + color: css('--color-text-primary'), + }, + '.surface-black': { + backgroundColor: css('--color-surface-black'), + color: css('--color-text-white'), + }, + }); + }, + { + theme: { + colors: { + ...colors, + brand: css('--color-surface-brand'), + }, + backgroundColor: ({ theme }) => ({ + ...theme('colors'), + + primary: css('--color-surface-primary'), + secondary: css('--color-surface-secondary'), + inverse: css('--color-surface-inverse'), + subtle: css('--color-surface-subtle'), + + interactive: css('--color-interactive-surface'), + 'interactive-hover': css('--color-interactive-hover'), + 'interactive-active': css('--color-interactive-active'), + 'interactive-error': css('--color-interactive-danger-surface'), + + 'interactive-secondary-hover': css( + '--color-interactive-secondary-hover', + ), + 'interactive-secondary-active': css( + '--color-interactive-secondary-active', + ), + 'interactive-table-hover': css('--color-interactive-table-hover'), + + information: css('--color-surface-information'), + success: css('--color-surface-success'), + warning: css('--color-surface-warning'), + danger: css('--color-surface-danger'), + 'code-block': css('--color-surface-code-block'), + + DEFAULT: css('--color-surface-primary'), + }), + borderColor: ({ theme }) => ({ + ...theme('colors'), + primary: css('--color-border-primary'), + secondary: css('--color-border-secondary'), + subtle: css('--color-border-subtle'), + interactive: css('--color-interactive-surface'), + 'interactive-hover': css('--color-interactive-hover'), + inverse: css('--color-border-inverse'), + table: css('--color-border-table'), + 'table-related-hover': css('--color-surface-table-related-hover'), + information: css('--color-border-information'), + success: css('--color-border-success'), + warning: css('--color-border-warning'), + danger: css('--color-border-danger'), + + DEFAULT: css('--color-border-primary'), + }), + ringColor: ({ theme }) => ({ + ...theme('colors'), + primary: css('--color-border-focus-info'), + danger: css('--color-border-focus-danger'), + success: css('--color-surface-success-loud'), + brand: css('--color-surface-brand'), + }), + textColor: ({ theme }) => ({ + ...theme('colors'), + primary: css('--color-text-primary'), + secondary: css('--color-text-secondary'), + subtle: css('--color-text-subtle'), + inverse: css('--color-text-inverse'), + brand: css('--color-text-brand'), + + danger: css('--color-text-danger'), + information: css('--color-text-information'), + success: css('--color-text-success'), + warning: css('--color-text-warning'), + + DEFAULT: css('--color-text-primary'), + }), + caretColor: ({ theme }) => ({ + ...theme('colors'), + + danger: css('--color-text-danger'), + }), + extend: { + transitionProperty: { + width: 'width', + height: 'height', + left: 'left', + right: 'right', + }, + }, + }, + }, +); + +export default temporal; +export { colors, textStyles }; diff --git a/src/lib/theme/preset.ts b/src/lib/theme/preset.ts new file mode 100644 index 000000000..2fe92db24 --- /dev/null +++ b/src/lib/theme/preset.ts @@ -0,0 +1,10 @@ +import type { Config } from 'tailwindcss'; + +import temporal, { textStyles } from './plugin'; + +const config = { + content: ['./src/**/*.{html,js,svelte,ts}'], + plugins: [temporal, textStyles], +} satisfies Config; + +export default config; diff --git a/src/lib/theme/types.d.ts b/src/lib/theme/types.d.ts new file mode 100644 index 000000000..aef722252 --- /dev/null +++ b/src/lib/theme/types.d.ts @@ -0,0 +1,32 @@ +type PaletteColor = import('./colors').PaletteColor; +type Palette = import('./colors').Palette; + +type RGB = `${number} ${number} ${number}`; +type HexColor = `#${string}`; + +type CSSVariable = `--${string}`; +type ColorVariables = Readonly< + Record +>; + +type Shade = + | 50 + | 100 + | 200 + | 300 + | 400 + | 500 + | 600 + | 700 + | 800 + | 900 + | 950 + | 'DEFAULT'; + +type Shades = Record; + +type Color = [PaletteColor, Shade | undefined] | HexColor; + +type ColorName = + | Exclude + | `${PaletteColor}.${Shade}`; diff --git a/src/lib/theme/utilities.ts b/src/lib/theme/utilities.ts new file mode 100644 index 000000000..684501826 --- /dev/null +++ b/src/lib/theme/utilities.ts @@ -0,0 +1,39 @@ +import { colors, palette } from './colors'; +import type { Variable } from './variables'; + +const removeHexPrefix = (hex: HexColor) => hex.replace('#', ''); + +export const rgb = (hexColor: HexColor): RGB => { + let hex = removeHexPrefix(hexColor); + hex = hex.length === 3 ? hex.replace(/./g, '$&$&') : hex; + const r = parseInt(hex.substring(0, 2), 16); + const g = parseInt(hex.substring(2, 4), 16); + const b = parseInt(hex.substring(4, 6), 16); + return `${r} ${g} ${b}`; +}; + +export const css = (variable: Variable) => `rgb(var(${variable}))`; + +export const toColor = (name: ColorName): RGB => { + const [paletteColor, shade] = name.split('.') as [PaletteColor, Shade]; + const color = colors[paletteColor]; + if (isHexColor(color)) return rgb(color); + if (isPaletteColor(paletteColor)) { + const color = palette[paletteColor]; + if (isShade(shade)) return rgb(color[shade]); + return rgb(color.DEFAULT); + } +}; + +export const isHexColor = (color: unknown): color is HexColor => { + if (typeof color !== 'string') return false; + return /^#[0-9A-F]{6}$/i.test(color); +}; + +export const isPaletteColor = (color: string): color is PaletteColor => + color in palette; + +export const isShade = (shade: unknown): shade is Shade => + typeof shade === 'number' || + typeof Number(shade) === 'number' || + shade === 'DEFAULT'; diff --git a/src/lib/theme/variables.ts b/src/lib/theme/variables.ts new file mode 100644 index 000000000..95655bbe0 --- /dev/null +++ b/src/lib/theme/variables.ts @@ -0,0 +1,229 @@ +import { toColor } from './utilities'; + +export type Variable = keyof typeof variables; + +const light: Record = {}; +const dark: Record = {}; + +export const variables = { + // Text + '--color-text-black': { + light: 'space-black', + dark: 'space-black', + }, + '--color-text-white': { + light: 'off-white', + dark: 'off-white', + }, + '--color-text-primary': { + light: 'space-black', + dark: 'off-white', + }, + '--color-text-secondary': { + light: 'slate.800', + dark: 'slate.300', + }, + '--color-text-inverse': { + light: 'off-white', + dark: 'space-black', + }, + '--color-text-subtle': { + light: 'slate.700', + dark: 'slate.700', + }, + '--color-text-danger': { + light: 'red.700', + dark: 'red.400', + }, + '--color-text-information': { + light: 'indigo.600', + dark: 'indigo.500', + }, + '--color-text-success': { + light: 'green.700', + dark: 'green.600', + }, + '--color-text-warning': { + light: 'yellow.600', + dark: 'yellow.500', + }, + '--color-text-pink': { + light: 'pink.800', + dark: 'pink.400', + }, + '--color-text-brand': { + light: 'indigo.600', + dark: 'indigo.400', + }, + // Surface + '--color-surface-background': { + light: 'off-white', + dark: 'space-black', + }, + '--color-surface-primary': { + light: 'white', + dark: 'black', + }, + '--color-surface-secondary': { + light: 'off-white', + dark: 'off-black', + }, + '--color-surface-subtle': { + light: 'slate.100', + dark: 'slate.800', + }, + '--color-surface-table': { + light: 'space-black', + dark: 'slate.900', + }, + '--color-surface-table-header': { + light: 'slate.50', + dark: 'slate.800', + }, + '--color-surface-success': { + light: 'green.100', + dark: 'green.950', + }, + '--color-surface-success-loud': { + light: 'green.600', + dark: 'green.600', + }, + '--color-surface-information': { + light: 'indigo.100', + dark: 'indigo.950', + }, + '--color-surface-information-loud': { + light: 'indigo.600', + dark: 'indigo.600', + }, + '--color-surface-danger': { + light: 'red.100', + dark: 'red.950', + }, + '--color-surface-warning': { + light: 'yellow.100', + dark: 'yellow.950', + }, + '--color-surface-brand': { + light: 'indigo.600', + dark: 'indigo.500', + }, + '--color-surface-inverse': { + light: 'space-black', + dark: 'off-white', + }, + '--color-surface-black': { + light: 'black', + dark: 'black', + }, + '--color-surface-code-block': { + light: 'slate.50', + dark: 'code-black', + }, + // Interactive + '--color-interactive-surface': { + light: 'indigo.600', + dark: 'indigo.600', + }, + '--color-interactive-hover': { + light: 'indigo.700', + dark: 'indigo.700', + }, + '--color-interactive-active': { + light: 'indigo.800', + dark: 'indigo.900', + }, + '--color-interactive-secondary-surface': { + light: 'white', + dark: 'slate.500', + }, + '--color-interactive-secondary-hover': { + light: 'slate.50', + dark: 'slate.900', + }, + '--color-interactive-secondary-active': { + light: 'slate.200', + dark: 'slate.800', + }, + '--color-interactive-danger-surface': { + light: 'red.300', + dark: 'red.300', + }, + '--color-interactive-danger-hover': { + light: 'red.400', + dark: 'red.400', + }, + '--color-interactive-danger-active': { + light: 'red.500', + dark: 'red.500', + }, + '--color-interactive-ghost-hover': { + light: 'slate.100', + dark: 'slate.700', + }, + '--color-interactive-ghost-active': { + light: 'slate.100', + dark: 'slate.950', + }, + '--color-interactive-table-hover': { + light: 'indigo.200', + dark: 'slate.700', + }, + '--color-surface-table-related-hover': { + light: 'indigo.100', + dark: 'slate.900', + }, + // Border + '--color-border-primary': { + light: 'space-black', + dark: 'slate.400', + }, + '--color-border-secondary': { + light: 'slate.300', + dark: 'slate.700', + }, + '--color-border-subtle': { + light: 'slate.200', + dark: 'slate.800', + }, + '--color-border-table': { + light: 'space-black', + dark: 'slate.900', + }, + '--color-border-inverse': { + light: 'off-white', + dark: 'space-black', + }, + '--color-border-information': { + light: 'indigo.600', + dark: 'indigo.500', + }, + '--color-border-success': { + light: 'green.600', + dark: 'green.700', + }, + '--color-border-warning': { + light: 'yellow.400', + dark: 'yellow.700', + }, + '--color-border-danger': { + light: 'red.500', + dark: 'red.400', + }, + '--color-border-focus-info': { + light: 'indigo.600', + dark: 'indigo.600', + }, + '--color-border-focus-danger': { + light: 'red.600', + dark: 'red.600', + }, +} as const satisfies ColorVariables; + +for (const key in variables) { + const value = variables[key]; + light[key] = toColor(value.light); + dark[key] = toColor(value.dark); +} + +export { light, dark }; diff --git a/src/lib/types/api.ts b/src/lib/types/api.ts new file mode 100644 index 000000000..480da8c8b --- /dev/null +++ b/src/lib/types/api.ts @@ -0,0 +1,168 @@ +import type { ArchiveFilterParameters, FilterParameters } from './workflows'; + +export type WorkflowsAPIRoutePath = + | 'workflows' + | 'workflows.archived' + | 'workflows.count'; + +export type WorkflowAPIRoutePath = + | 'workflow' + | 'workflow.terminate' + | 'workflow.cancel' + | 'workflow.reset' + | 'events.raw' + | 'events.ascending' + | 'events.descending'; + +export type WorkflowSignalAPIRoutePath = 'workflow.signal'; + +export type WorkflowUpdateAPIRoutePath = 'workflow.update'; + +export type WorkflowQueryAPIRoutePath = 'query'; + +export type WorkflowActivitiesAPIRoutePath = + | 'activity.complete' + | 'activity.fail' + | 'activity.pause' + | 'activity.unpause' + | 'activity.reset' + | 'activity.update-options'; + +export type BatchAPIRoutePath = 'batch-operations.list' | 'batch-operations'; + +export type NamespaceAPIRoutePath = 'namespace'; + +export type TaskQueueAPIRoutePath = + | 'task-queue' + | 'task-queue.compatibility' + | 'task-queue.rules'; +export type ParameterlessAPIRoutePath = + | 'systemInfo' + | 'cluster' + | 'settings' + | 'user' + | 'nexus-endpoints' + | 'namespaces'; +export type WorkerAPIRoutePath = 'worker-task-reachability'; +export type SchedulesAPIRoutePath = 'schedules'; +export type ScheduleAPIRoutePath = + | 'schedule' + | 'schedule.patch' + | 'schedule.edit'; +export type SearchAttributesRoutePath = 'search-attributes'; +export type NexusAPIRoutePath = 'nexus-endpoint' | 'nexus-endpoint.update'; +export type WorkerDeploymentsAPIRoutePath = 'worker-deployments'; +export type WorkerDeploymentAPIRoutePath = 'worker-deployment'; +export type WorkerDeploymentVersionAPIRoutePath = 'worker-deployment-version'; + +export type APIRoutePath = + | ParameterlessAPIRoutePath + | ScheduleAPIRoutePath + | SchedulesAPIRoutePath + | SearchAttributesRoutePath + | TaskQueueAPIRoutePath + | WorkerAPIRoutePath + | WorkflowAPIRoutePath + | WorkflowSignalAPIRoutePath + | WorkflowUpdateAPIRoutePath + | WorkflowQueryAPIRoutePath + | WorkflowActivitiesAPIRoutePath + | WorkflowsAPIRoutePath + | NamespaceAPIRoutePath + | BatchAPIRoutePath + | NexusAPIRoutePath + | WorkerDeploymentsAPIRoutePath + | WorkerDeploymentAPIRoutePath + | WorkerDeploymentVersionAPIRoutePath; + +export type APIRouteParameters = { + namespace: string; + workflowId: string; + scheduleId: string; + // feel like this might not be the "right" spot for this. + runId: string; + batchJobId: string; + queue: string; + queryType: string; + signalName: string; + updateName: string; + activityId: string; + endpointId: string; + deploymentName: string; + version: string; +}; + +export type WorkflowListRouteParameters = Pick; +export type NamespaceRouteParameters = Pick; +export type ScheduleListRouteParameters = Pick; +export type SearchAttributesRouteParameters = Pick< + APIRouteParameters, + 'namespace' +>; + +export type WorkflowSignalRouteParameters = Pick< + APIRouteParameters, + 'namespace' | 'workflowId' | 'signalName' +>; + +export type WorkflowUpdateRouteParameters = Pick< + APIRouteParameters, + 'namespace' | 'workflowId' | 'updateName' +>; + +export type WorkflowRouteParameters = Pick< + APIRouteParameters, + 'namespace' | 'workflowId' +>; + +export type WorkflowRawHistoryRouteParameters = Pick< + APIRouteParameters, + 'namespace' | 'workflowId' | 'runId' +>; + +export type WorkflowQueryRouteParameters = Pick< + APIRouteParameters, + 'namespace' | 'workflowId' | 'queryType' +>; + +export type WorkflowActivitiesRouteParameters = Pick< + APIRouteParameters, + 'namespace' +>; + +export type BatchRouteParameters = Pick< + APIRouteParameters, + 'namespace' | 'batchJobId' +>; + +export type TaskQueueRouteParameters = Pick< + APIRouteParameters, + 'namespace' | 'queue' +>; + +export type ValidWorkflowEndpoints = WorkflowsAPIRoutePath; + +export type ValidWorkflowParameters = + | ArchiveFilterParameters + | FilterParameters; + +export type ScheduleRouteParameters = Pick< + APIRouteParameters, + 'namespace' | 'scheduleId' +>; + +export type NexusRouteParameters = Pick; + +export type WorkerDeploymentListRouteParameters = Pick< + APIRouteParameters, + 'namespace' +>; +export type WorkerDeploymentRouteParameters = Pick< + APIRouteParameters, + 'namespace' | 'deploymentName' +>; + +export type WorkerDeploymentVersionRouteParameters = Pick< + APIRouteParameters, + 'namespace' | 'version' +>; diff --git a/src/lib/types/batch.ts b/src/lib/types/batch.ts new file mode 100644 index 000000000..c13d33720 --- /dev/null +++ b/src/lib/types/batch.ts @@ -0,0 +1,63 @@ +export type BatchOperationType = + | 'Cancel' + | 'Terminate' + | 'Reset' + | 'Signal' + | 'Delete' + | 'Unspecified'; + +export type BatchOperationState = + | 'Running' + | 'Completed' + | 'Failed' + | 'Unspecified'; + +export type DescribeBatchOperationResponse = { + operationType: BatchOperationType; + jobId: string; + state: BatchOperationState; + startTime: string; + closeTime: string; + totalOperationCount: string; + completeOperationCount: string; + failureOperationCount: string; + identity: string; + reason: string; +}; + +export type ListBatchOperationsResponse = { + nextPageToken: string | null; + operationInfo: APIBatchOperationInfo[]; +}; + +export type BatchOperations = { + nextPageToken: string | null; + operations: BatchOperationInfo[]; +}; + +export type APIBatchOperationInfo = { + startTime: string; + closeTime: string; + state: BatchOperationState; + jobId: string; +}; + +export type BatchOperationInfo = { + startTime: string; + closeTime: string; + state: BatchOperationState; + jobId: string; +}; + +export type BatchOperation = { + operationType: BatchOperationType; + jobId: string; + state: BatchOperationState; + startTime: string; + closeTime: string; + totalOperationCount: number; + completeOperationCount: number; + failureOperationCount: number; + identity: string; + reason: string; +}; diff --git a/src/lib/types/deployments.ts b/src/lib/types/deployments.ts new file mode 100644 index 000000000..b02fae145 --- /dev/null +++ b/src/lib/types/deployments.ts @@ -0,0 +1,125 @@ +import type { Timestamp } from '@temporalio/common'; + +export interface DeploymentParameters { + namespace: string; + deploymentName: string; +} + +export interface DeploymentVersionParameters { + namespace: string; + version: string; +} +export interface WorkerDeploymentVersion { + buildId: string; + deploymentName: string; +} +export interface RoutingConfig { + currentVersion?: string; + currentDeploymentVersion?: WorkerDeploymentVersion; + rampingVersion?: string; + rampingDeploymentVersion?: WorkerDeploymentVersion; + rampingVersionPercentage?: number; + currentVersionChangedTime?: Timestamp; + rampingVersionChangedTime?: Timestamp; + rampingVersionPercentageChangedTime?: Timestamp; +} + +export interface WorkerDeploymentSummary { + name: string; + createTime: Timestamp; + routingConfig: RoutingConfig; + latestVersionSummary?: VersionSummaryNew; + currentVersionSummary: VersionSummaryNew; + rampingVersionSummary?: VersionSummaryNew; +} + +export interface ListWorkerDeploymentsResponse { + nextPageToken: string; + workerDeployments: WorkerDeploymentSummary[]; +} + +export function isVersionSummaryNew( + version: VersionSummary, +): version is VersionSummaryNew { + return 'deploymentVersion' in version; +} + +export type VersionSummary = VersionSummaryOld | VersionSummaryNew; +export interface VersionSummaryOld { + version: string; + createTime: Timestamp; + drainageStatus: string; +} + +export interface VersionSummaryNew { + version: string; + status?: string; + drainageStatus?: string; + deploymentVersion?: WorkerDeploymentVersion; + createTime: Timestamp; + drainageInfo?: { + lastChangedTime?: Timestamp; + lastCheckedTime?: Timestamp; + }; + currentSinceTime?: Timestamp; + rampingSinceTime?: Timestamp; + routingUpdateTime?: Timestamp; + firstActivationTime?: Timestamp; + lastDeactivationTime?: Timestamp; +} + +export interface WorkerDeploymentInfo extends WorkerDeploymentSummary { + lastModifierIdentity: string; + versionSummaries: VersionSummary[]; +} + +export interface WorkerDeploymentResponse { + conflictToken: string; + workerDeploymentInfo: WorkerDeploymentInfo; +} + +export interface TaskQueueInfo { + name: string; + type: string; +} + +export interface VersioningInfo { + behavior: string; + version: string; +} + +export interface WorkerDeploymentVersionInfo { + version: string; + deploymentName: string; + createTime: Timestamp; + routingChangedTime: Timestamp; + currentSinceTime: Timestamp; + rampingSinceTime: Timestamp; + rampPercentage: number; + taskQueueInfos: TaskQueueInfo[]; + drainageInfo: { + status: string; + lastChangedTime: Timestamp; + lastCheckedTime: Timestamp; + }; + metadata: { + entries: Record; + }; +} + +export interface WorkerDeploymentVersionResponse { + workerDeploymentVersionInfo: WorkerDeploymentVersionInfo; +} + +export const VersioningBehaviorEnum = { + Pinned: 'Pinned', + AutoUpgrade: 'AutoUpgrade', +}; + +export type DeploymentStatus = + | 'Ramping' + | 'Current' + | 'Latest' + | 'Draining' + | 'Drained' + | 'Inactive'; diff --git a/src/lib/types/events.ts b/src/lib/types/events.ts new file mode 100644 index 000000000..1fef868ff --- /dev/null +++ b/src/lib/types/events.ts @@ -0,0 +1,301 @@ +import type { Timestamp } from '@temporalio/common'; + +import type { EventGroup } from '$lib/models/event-groups/event-groups'; +import type { ActivityOptions } from '$lib/types'; + +import type { Replace, Settings } from './global'; + +export type EventHistory = Replace< + import('$lib/types').History, + { events: HistoryEvent[] } +>; + +export type EventLink = { + workflowEvent: { + eventRef?: { + eventType: string; + eventId?: string; + }; + requestIdRef?: { + requestId: string; + eventType: string; + }; + namespace: string; + workflowId: string; + runId: string; + }; + + batchJob?: { + jobId: string; + }; +}; + +export type HistoryEvent = Replace< + import('$lib/types').HistoryEvent, + { eventType: EventType; eventId: string; links?: EventLink[] } +>; + +export type GetWorkflowExecutionHistoryResponse = Replace< + import('$lib/types').GetWorkflowExecutionHistoryResponse, + { history: EventHistory } +>; + +export type PendingActivityInfo = Replace< + import('$lib/types').PendingActivityInfo, + { activityId: string } +>; + +export type Payload = { + metadata?: { [k: string]: string }; + data?: string; +}; + +export type PauseInfo = { + manual: { + reason: string; + identity: string; + }; + pauseTime: Timestamp; +}; + +export type PendingActivity = Replace< + PendingActivityInfo, + { + id: string; + state: PendingActivityState; + activityType?: string; + pauseInfo?: PauseInfo; + activityOptions?: ActivityOptions; + } +>; + +export type PendingActivityState = + | 'Unspecified' + | 'Scheduled' + | 'Started' + | 'CancelRequested'; + +export type PendingChildren = import('$lib/types').PendingChildrenInfo; +export type PendingNexusOperation = import('$lib/types').PendingNexusInfo & { + scheduledEventId: string; + scheduleToCloseTimeout: string; +}; +export type Callbacks = import('$lib/types').CallbackInfo[]; + +export type EventRequestMetadata = { + namespace: string; + settings: Settings; + accessToken: string; +}; + +export type EventWithMetadata = { + historyEvent: HistoryEvent; +} & EventRequestMetadata; + +export type EventType = import('$lib/utilities/is-event-type').EventType; +export type ResetEventType = + import('$lib/utilities/is-event-type').ResetEventType; + +export type EventTypeCategory = + import('$lib/models/event-history/get-event-categorization').EventTypeCategory; + +export type EventClassification = + import('$lib/models/event-history/get-event-classification').EventClassification; + +export interface WorkflowEvent extends HistoryEvent { + id: string; + attributes: EventAttribute; + timestamp: string; + classification: EventClassification; + category: EventTypeCategory; + name: EventType; + links?: EventLink[]; + billableActions?: number; +} + +export type WorkflowEvents = WorkflowEvent[]; + +export type PendingActivityWithMetadata = { + activity: PendingActivity; +} & EventRequestMetadata; + +export type CommonEventKey = + | 'id' + | 'eventType' + | 'attributes' + | 'eventId' + | 'eventTime' + | 'version' + | 'taskId' + | 'timestamp' + | 'classification' + | 'category' + | 'workerMayIgnore' + | 'name'; + +export type CommonHistoryEvent = Pick; + +export type EventAttributeKey = keyof Omit; +export type EventAttribute = HistoryEvent[EventAttributeKey]; +export type EventAttributesWithType = + HistoryEvent[K] & { + type: K; + }; + +export type EventWithAttributes = + CommonHistoryEvent & + Pick & { attributes: EventAttributesWithType }; + +export type ActivityEvent = ActivityTaskScheduledEvent & + ActivityTaskStartedEvent & + ActivityTaskCompletedEvent & + ActivityTaskFailedEvent & + ActivityTaskTimedOutEvent & + ActivityTaskCancelRequestedEvent & + ActivityTaskCanceledEvent; + +export type TimerEvent = TimerCanceledEvent & + TimerStartedEvent & + TimerFiredEvent; + +export type SignalEvent = SignalExternalWorkflowExecutionInitiatedEvent & + SignalExternalWorkflowExecutionFailedEvent & + WorkflowExecutionSignaledEvent; + +export type MarkerEvent = MarkerRecordedEvent; + +export type ChildEvent = StartChildWorkflowExecutionInitiatedEvent & + StartChildWorkflowExecutionFailedEvent & + ChildWorkflowExecutionStartedEvent & + ChildWorkflowExecutionCompletedEvent & + ChildWorkflowExecutionFailedEvent & + ChildWorkflowExecutionCanceledEvent & + ChildWorkflowExecutionTimedOutEvent & + ChildWorkflowExecutionTerminatedEvent; + +export type EventView = 'compact' | 'feed' | 'json'; +export type TaskQueueView = 'workers' | 'versioning'; + +export type IterableEvent = WorkflowEvent | EventGroup; + +export type WorkflowEventWithPending = + | WorkflowEvent + | PendingActivity + | PendingNexusOperation; + +export type IterableEventWithPending = EventGroup | WorkflowEventWithPending; + +export type WorkflowExecutionStartedEvent = + EventWithAttributes<'workflowExecutionStartedEventAttributes'>; +export type WorkflowExecutionCompletedEvent = + EventWithAttributes<'workflowExecutionCompletedEventAttributes'>; +export type WorkflowExecutionOptionsUpdatedEvent = + EventWithAttributes<'workflowExecutionOptionsUpdatedEventAttributes'>; +export type WorkflowExecutionFailedEvent = + EventWithAttributes<'workflowExecutionFailedEventAttributes'>; +export type WorkflowExecutionTimedOutEvent = + EventWithAttributes<'workflowExecutionTimedOutEventAttributes'>; +export type WorkflowTaskScheduledEvent = + EventWithAttributes<'workflowTaskScheduledEventAttributes'>; +export type WorkflowTaskStartedEvent = + EventWithAttributes<'workflowTaskStartedEventAttributes'>; +export type WorkflowTaskCompletedEvent = + EventWithAttributes<'workflowTaskCompletedEventAttributes'>; +export type WorkflowTaskTimedOutEvent = + EventWithAttributes<'workflowTaskTimedOutEventAttributes'>; +export type WorkflowTaskFailedEvent = + EventWithAttributes<'workflowTaskFailedEventAttributes'>; +export type ActivityTaskScheduledEvent = + EventWithAttributes<'activityTaskScheduledEventAttributes'>; +export type ActivityTaskStartedEvent = + EventWithAttributes<'activityTaskStartedEventAttributes'>; +export type ActivityTaskCompletedEvent = + EventWithAttributes<'activityTaskCompletedEventAttributes'>; +export type ActivityTaskFailedEvent = + EventWithAttributes<'activityTaskFailedEventAttributes'>; +export type ActivityTaskTimedOutEvent = + EventWithAttributes<'activityTaskTimedOutEventAttributes'>; +export type TimerStartedEvent = + EventWithAttributes<'timerStartedEventAttributes'>; +export type TimerFiredEvent = EventWithAttributes<'timerFiredEventAttributes'>; +export type ActivityTaskCancelRequestedEvent = + EventWithAttributes<'activityTaskCancelRequestedEventAttributes'>; +export type ActivityTaskCanceledEvent = + EventWithAttributes<'activityTaskCanceledEventAttributes'>; +export type TimerCanceledEvent = + EventWithAttributes<'timerCanceledEventAttributes'>; +export type MarkerRecordedEvent = + EventWithAttributes<'markerRecordedEventAttributes'>; +export type WorkflowExecutionSignaledEvent = + EventWithAttributes<'workflowExecutionSignaledEventAttributes'>; +export type WorkflowExecutionTerminatedEvent = + EventWithAttributes<'workflowExecutionTerminatedEventAttributes'>; +export type WorkflowExecutionCancelRequestedEvent = + EventWithAttributes<'workflowExecutionCancelRequestedEventAttributes'>; +export type WorkflowExecutionCanceledEvent = + EventWithAttributes<'workflowExecutionCanceledEventAttributes'>; +export type RequestCancelExternalWorkflowExecutionInitiatedEvent = + EventWithAttributes<'requestCancelExternalWorkflowExecutionInitiatedEventAttributes'>; +export type RequestCancelExternalWorkflowExecutionFailedEvent = + EventWithAttributes<'requestCancelExternalWorkflowExecutionFailedEventAttributes'>; +export type ExternalWorkflowExecutionCancelRequestedEvent = + EventWithAttributes<'externalWorkflowExecutionCancelRequestedEventAttributes'>; +export type WorkflowExecutionContinuedAsNewEvent = + EventWithAttributes<'workflowExecutionContinuedAsNewEventAttributes'>; +export type StartChildWorkflowExecutionInitiatedEvent = + EventWithAttributes<'startChildWorkflowExecutionInitiatedEventAttributes'>; +export type StartChildWorkflowExecutionFailedEvent = + EventWithAttributes<'startChildWorkflowExecutionFailedEventAttributes'>; +export type ChildWorkflowExecutionStartedEvent = + EventWithAttributes<'childWorkflowExecutionStartedEventAttributes'>; +export type ChildWorkflowExecutionCompletedEvent = + EventWithAttributes<'childWorkflowExecutionCompletedEventAttributes'>; +export type ChildWorkflowExecutionFailedEvent = + EventWithAttributes<'childWorkflowExecutionFailedEventAttributes'>; +export type ChildWorkflowExecutionCanceledEvent = + EventWithAttributes<'childWorkflowExecutionCanceledEventAttributes'>; +export type ChildWorkflowExecutionTimedOutEvent = + EventWithAttributes<'childWorkflowExecutionTimedOutEventAttributes'>; +export type ChildWorkflowExecutionTerminatedEvent = + EventWithAttributes<'childWorkflowExecutionTerminatedEventAttributes'>; +export type SignalExternalWorkflowExecutionInitiatedEvent = + EventWithAttributes<'signalExternalWorkflowExecutionInitiatedEventAttributes'>; +export type SignalExternalWorkflowExecutionFailedEvent = + EventWithAttributes<'signalExternalWorkflowExecutionFailedEventAttributes'>; +export type ExternalWorkflowExecutionSignaledEvent = + EventWithAttributes<'externalWorkflowExecutionSignaledEventAttributes'>; +export type UpsertWorkflowSearchAttributesEvent = + EventWithAttributes<'upsertWorkflowSearchAttributesEventAttributes'>; +export type WorkflowExecutionUpdateAdmittedEvent = + EventWithAttributes<'workflowExecutionUpdateAdmittedEventAttributes'>; +export type WorkflowExecutionUpdateAcceptedEvent = + EventWithAttributes<'workflowExecutionUpdateAcceptedEventAttributes'>; +export type WorkflowExecutionUpdateRejectedEvent = + EventWithAttributes<'workflowExecutionUpdateRejectedEventAttributes'>; +export type WorkflowExecutionUpdateCompletedEvent = + EventWithAttributes<'workflowExecutionUpdateCompletedEventAttributes'>; +export type NexusOperationScheduledEvent = + EventWithAttributes<'nexusOperationScheduledEventAttributes'>; +export type NexusOperationStartedEvent = + EventWithAttributes<'nexusOperationStartedEventAttributes'>; +export type NexusOperationCompletedEvent = + EventWithAttributes<'nexusOperationCompletedEventAttributes'>; +export type NexusOperationFailedEvent = + EventWithAttributes<'nexusOperationFailedEventAttributes'>; +export type NexusOperationCanceledEvent = + EventWithAttributes<'nexusOperationCanceledEventAttributes'>; +export type NexusOperationTimedOutEvent = + EventWithAttributes<'nexusOperationTimedOutEventAttributes'>; +export type NexusOperationCancelRequestedEvent = + EventWithAttributes<'nexusOperationCancelRequestedEventAttributes'>; +export type WorkflowPropertiesModifiedEvent = + EventWithAttributes<'workflowPropertiesModifiedEventAttributes'>; + +export type FailActivityTaskRequest = + import('$lib/types').ActivityTaskFailedByIdRequest; +export type FailActivityTaskResponse = + import('$lib/types').ActivityTaskFailedByIdResponse; +export type CompleteActivityTaskRequest = + import('$lib/types').ActivityTaskCompletedByIdRequest; +export type CompleteActivityTaskResponse = + import('$lib/types').ActivityTaskCompletedByIdResponse; diff --git a/src/lib/types/global.ts b/src/lib/types/global.ts new file mode 100644 index 000000000..6d2cb0679 --- /dev/null +++ b/src/lib/types/global.ts @@ -0,0 +1,159 @@ +import type { IconName } from '$lib/holocene/icon'; + +export type NamespaceListItem = { + namespace: string; + onClick: (namspace: string) => void; +}; + +export type Optional = Omit & + Partial>; + +export type Replace = Omit< + T, + keyof U +> & + U; + +export type Only = { + [X in keyof Pick]-?: true; +} & { + [X in keyof Omit]: never; +}; + +export type Without = { [P in Exclude]?: never }; +export type XOR = T | U extends object + ? (Without & U) | (Without & T) + : T | U; + +/** + * Given an object with nested properties + * returns a Union of the valid nested paths concatenated with a `.` + * @example + * ``` + * const myNestedObject = { + * a: { b: 'c' }, + * b: 'c', + * } + * + * type MyNestedObject = Leaves; // 'b' | 'a.b'; + * ``` + */ +export type Leaves = T extends object + ? { + [K in keyof T]: `${Exclude}${Leaves extends never + ? '' + : `.${Leaves}`}`; + }[keyof T] + : never; + +export type Eventual = T | PromiseLike; + +export type NamespaceScopedRequest = { namespace: string }; + +export type NextPageToken = Uint8Array | string; +export type WithNextPageToken = { nextPageToken?: NextPageToken }; +export type WithoutNextPageToken = Omit; + +export type PaginationCallbacks = { + onStart?: () => void; + onUpdate?: ( + full: WithoutNextPageToken, + current: WithoutNextPageToken, + ) => void; + onComplete?: (finalProps: WithoutNextPageToken) => void; + onError?: (error: unknown) => void; +}; + +export interface NetworkError { + statusCode: number; + statusText: string; + response: Response; + message?: string; +} + +export type Settings = { + auth: { + enabled: boolean; + options: string[]; + }; + bannerText: string; + baseUrl: string; + codec: { + endpoint?: string; + passAccessToken?: boolean; + includeCredentials?: boolean; + customErrorMessage?: { + default?: { + message?: string; + link?: string; + }; + }; + }; + defaultNamespace: string; + disableWriteActions: boolean; + workflowTerminateDisabled: boolean; + workflowCancelDisabled: boolean; + workflowSignalDisabled: boolean; + workflowUpdateDisabled: boolean; + workflowResetDisabled: boolean; + hideWorkflowQueryErrors: boolean; + batchActionsDisabled: boolean; + activityCommandsDisabled: boolean; + showTemporalSystemNamespace: boolean; + notifyOnNewVersion: boolean; + feedbackURL: string; + runtimeEnvironment: { + isCloud: boolean; + isLocal: boolean; + envOverride: boolean; + }; + version: string; +}; + +export type User = { + accessToken?: string; + idToken?: string; + name?: string; + given_name?: string; + family_name?: string; + middle_name?: string; + nickname?: string; + preferred_username?: string; + profile?: string; + picture?: string; + website?: string; + email?: string; + email_verified?: boolean; + gender?: string; + birthdate?: string; + zoneinfo?: string; + locale?: string; + phone_number?: string; + phone_number_verified?: boolean; + address?: string; + updated_at?: string; + sub?: string; + //eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any; +}; +export type ClusterInformation = import('$lib/types').GetClusterInfoResponse; +export type SystemInformation = import('$lib/types').GetSystemInfoResponse; + +export type SelectOptionValue = number | string | boolean | undefined; + +export type BooleanString = 'true' | 'false'; + +export type DataEncoderStatus = 'notRequested' | 'success' | 'error'; + +export type NavLinkListItem = { + href: string; + icon: IconName; + label: string; + tooltip?: string; + external?: boolean; + divider?: boolean; + enabled?: boolean; + hidden?: boolean; + animate?: boolean; + isActive?: (path: string) => boolean; +}; diff --git a/src/lib/types/holocene.ts b/src/lib/types/holocene.ts new file mode 100644 index 000000000..8400f7b6d --- /dev/null +++ b/src/lib/types/holocene.ts @@ -0,0 +1,22 @@ +export type DataAttributes = { + // [index: `data-${string}`]: any; + 'data-testid'?: string; +}; + +export type ToastVariant = 'success' | 'error' | 'info' | 'warning' | 'primary'; + +export type ToastPosition = + | 'top-left' + | 'top-center' + | 'top-right' + | 'bottom-left' + | 'bottom-center' + | 'bottom-right'; + +export interface Toast { + message: string; + variant?: ToastVariant; + id?: string; + duration?: number; + link?: string; +} diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts new file mode 100644 index 000000000..1c7b8720e --- /dev/null +++ b/src/lib/types/index.ts @@ -0,0 +1,289 @@ +import type { google, temporal } from '@temporalio/proto'; + +// api.workflowservice + +export type DescribeNamespaceResponse = + temporal.api.workflowservice.v1.IDescribeNamespaceResponse; +export type DescribeWorkflowExecutionResponse = + temporal.api.workflowservice.v1.IDescribeWorkflowExecutionResponse; +export type ListNamespacesResponse = + temporal.api.workflowservice.v1.IListNamespacesResponse; +export type GetClusterInfoResponse = + temporal.api.workflowservice.v1.IGetClusterInfoResponse; +export type GetSystemInfoResponse = + temporal.api.workflowservice.v1.IGetSystemInfoResponse; +export type Capabilities = + temporal.api.workflowservice.v1.GetSystemInfoResponse.ICapabilities & { + nexus?: boolean; + }; +export type GetWorkflowExecutionHistoryResponse = + temporal.api.workflowservice.v1.IGetWorkflowExecutionHistoryResponse; +export type GetSearchAttributesResponse = + temporal.api.workflowservice.v1.IGetSearchAttributesResponse; +export type ListWorkflowExecutionsResponse = + temporal.api.workflowservice.v1.IListWorkflowExecutionsResponse; +export type ListScheduleResponse = + temporal.api.workflowservice.v1.IListSchedulesResponse; +export type DescribeScheduleResponse = + temporal.api.workflowservice.v1.IDescribeScheduleResponse; +export type Schedule = temporal.api.schedule.v1.ISchedule; +export type CreateScheduleRequest = + temporal.api.workflowservice.v1.ICreateScheduleRequest; +export type PatchScheduleRequest = + temporal.api.workflowservice.v1.IPatchScheduleRequest; +export type UpdateScheduleRequest = + temporal.api.workflowservice.v1.IUpdateScheduleRequest; +export type StartBatchOperationRequest = + temporal.api.workflowservice.v1.IStartBatchOperationRequest; +export type ResetWorkflowRequest = + temporal.api.workflowservice.v1.IResetWorkflowExecutionRequest; +export type UpdateWorkflowRequest = + temporal.api.workflowservice.v1.IUpdateWorkflowExecutionRequest; +export type UpdateWorkflowResponse = + temporal.api.workflowservice.v1.IUpdateWorkflowExecutionResponse; + +// api.history + +export type History = temporal.api.history.v1.IHistory; +export type HistoryEvent = temporal.api.history.v1.IHistoryEvent; +export type WorkflowExecutionStartedEventAttributes = + temporal.api.history.v1.IWorkflowExecutionStartedEventAttributes; +export type WorkflowExecutionCompletedEventAttributes = + temporal.api.history.v1.IWorkflowExecutionCompletedEventAttributes; +export type WorkflowExecutionFailedEventAttributes = + temporal.api.history.v1.IWorkflowExecutionFailedEventAttributes; +export type WorkflowExecutionTimedOutEventAttributes = + temporal.api.history.v1.IWorkflowExecutionTimedOutEventAttributes; +export type WorkflowTaskScheduledEventAttributes = + temporal.api.history.v1.IWorkflowTaskScheduledEventAttributes; +export type WorkflowTaskStartedEventAttributes = + temporal.api.history.v1.IWorkflowTaskStartedEventAttributes; +export type WorkflowTaskCompletedEventAttributes = + temporal.api.history.v1.IWorkflowTaskCompletedEventAttributes; +export type WorkflowTaskTimedOutEventAttributes = + temporal.api.history.v1.IWorkflowTaskTimedOutEventAttributes; +export type WorkflowTaskFailedEventAttributes = + temporal.api.history.v1.IWorkflowTaskFailedEventAttributes & { + cause?: import('$types/workflows').WorkflowTaskFailedCause; + }; +export type ActivityTaskScheduledEventAttributes = + temporal.api.history.v1.IActivityTaskScheduledEventAttributes; +export type ActivityTaskStartedEventAttributes = + temporal.api.history.v1.IActivityTaskStartedEventAttributes; +export type ActivityTaskCompletedEventAttributes = + temporal.api.history.v1.IActivityTaskCompletedEventAttributes; +export type ActivityTaskFailedEventAttributes = + temporal.api.history.v1.IActivityTaskFailedEventAttributes; +export type ActivityTaskTimedOutEventAttributes = + temporal.api.history.v1.IActivityTaskTimedOutEventAttributes; +export type TimerStartedEventAttributes = + temporal.api.history.v1.ITimerStartedEventAttributes; +export type TimerFiredEventAttributes = + temporal.api.history.v1.ITimerFiredEventAttributes; +export type ActivityTaskCancelRequestedEventAttributes = + temporal.api.history.v1.IActivityTaskCancelRequestedEventAttributes; +export type ActivityTaskCanceledEventAttributes = + temporal.api.history.v1.IActivityTaskCanceledEventAttributes; +export type TimerCanceledEventAttributes = + temporal.api.history.v1.ITimerCanceledEventAttributes; +export type MarkerRecordedEventAttributes = + temporal.api.history.v1.IMarkerRecordedEventAttributes; +export type WorkflowExecutionSignaledEventAttributes = + temporal.api.history.v1.IWorkflowExecutionSignaledEventAttributes; +export type WorkflowExecutionTerminatedEventAttributes = + temporal.api.history.v1.IWorkflowExecutionTerminatedEventAttributes; +export type WorkflowExecutionCancelRequestedEventAttributes = + temporal.api.history.v1.IWorkflowExecutionCancelRequestedEventAttributes; +export type WorkflowExecutionCanceledEventAttributes = + temporal.api.history.v1.IWorkflowExecutionCanceledEventAttributes; +export type RequestCancelExternalWorkflowExecutionInitiatedEventAttributes = + temporal.api.history.v1.IRequestCancelExternalWorkflowExecutionInitiatedEventAttributes; +export type RequestCancelExternalWorkflowExecutionFailedEventAttributes = + temporal.api.history.v1.IRequestCancelExternalWorkflowExecutionFailedEventAttributes; +export type ExternalWorkflowExecutionCancelRequestedEventAttributes = + temporal.api.history.v1.IExternalWorkflowExecutionCancelRequestedEventAttributes; +export type WorkflowExecutionContinuedAsNewEventAttributes = + temporal.api.history.v1.IWorkflowExecutionContinuedAsNewEventAttributes; +export type StartChildWorkflowExecutionInitiatedEventAttributes = + temporal.api.history.v1.IStartChildWorkflowExecutionInitiatedEventAttributes; +export type StartChildWorkflowExecutionFailedEventAttributes = + temporal.api.history.v1.IStartChildWorkflowExecutionFailedEventAttributes; +export type ChildWorkflowExecutionStartedEventAttributes = + temporal.api.history.v1.IChildWorkflowExecutionStartedEventAttributes; +export type ChildWorkflowExecutionCompletedEventAttributes = + temporal.api.history.v1.IChildWorkflowExecutionCompletedEventAttributes; +export type ChildWorkflowExecutionFailedEventAttributes = + temporal.api.history.v1.IChildWorkflowExecutionFailedEventAttributes; +export type ChildWorkflowExecutionCanceledEventAttributes = + temporal.api.history.v1.IChildWorkflowExecutionCanceledEventAttributes; +export type ChildWorkflowExecutionTimedOutEventAttributes = + temporal.api.history.v1.IChildWorkflowExecutionTimedOutEventAttributes; +export type ChildWorkflowExecutionTerminatedEventAttributes = + temporal.api.history.v1.IChildWorkflowExecutionTerminatedEventAttributes; +export type SignalExternalWorkflowExecutionInitiatedEventAttributes = + temporal.api.history.v1.ISignalExternalWorkflowExecutionInitiatedEventAttributes; +export type SignalExternalWorkflowExecutionFailedEventAttributes = + temporal.api.history.v1.ISignalExternalWorkflowExecutionFailedEventAttributes; +export type ExternalWorkflowExecutionSignaledEventAttributes = + temporal.api.history.v1.IExternalWorkflowExecutionSignaledEventAttributes; +export type UpsertWorkflowSearchAttributesEventAttributes = + temporal.api.history.v1.IUpsertWorkflowSearchAttributesEventAttributes; +export type ActivityTaskFailedByIdRequest = + temporal.api.workflowservice.v1.IRespondActivityTaskFailedRequest; +export type ActivityTaskFailedByIdResponse = + temporal.api.workflowservice.v1.IRespondActivityTaskFailedResponse; +export type ActivityTaskCompletedByIdRequest = + temporal.api.workflowservice.v1.IRespondActivityTaskCompletedRequest; +export type ActivityTaskCompletedByIdResponse = + temporal.api.workflowservice.v1.IRespondActivityTaskCompletedResponse; +export type ActivityPauseRequest = + temporal.api.workflowservice.v1.IPauseActivityRequest; +export type ActivityPauseResponse = + temporal.api.workflowservice.v1.IPauseActivityResponse; +export type ActivityUnpauseRequest = + temporal.api.workflowservice.v1.IUnpauseActivityRequest; +export type ActivityUnpauseResponse = + temporal.api.workflowservice.v1.IUnpauseActivityResponse; +export type ActivityResetRequest = + temporal.api.workflowservice.v1.IResetActivityRequest; +export type ActivityResetResponse = + temporal.api.workflowservice.v1.IResetActivityResponse; +export type ActivityUpdateOptionsRequest = + temporal.api.workflowservice.v1.IUpdateActivityOptionsRequest; +export type ActivityUpdateOptionsResponse = + temporal.api.workflowservice.v1.IUpdateActivityOptionsResponse; +export type ActivityOptions = temporal.api.activity.v1.IActivityOptions; + +export type WorkflowPropertiesModifiedEventAttributes = + temporal.api.history.v1.IWorkflowPropertiesModifiedEventAttributes; + +// api.enums + +export type WorkflowExecutionStatus = + temporal.api.enums.v1.WorkflowExecutionStatus; +export type Severity = temporal.api.enums.v1.Severity; +export type ArchivalState = temporal.api.enums.v1.ArchivalState; +export type NamespaceState = temporal.api.enums.v1.NamespaceState; +export type TaskReachability = temporal.api.enums.v1.TaskReachability; +export type PendingNexusOperationState = + temporal.api.enums.v1.PendingNexusOperationState; +export type CallbackState = temporal.api.enums.v1.CallbackState; +export type PendingWorkflowTaskInfo = + temporal.api.workflow.v1.IPendingWorkflowTaskInfo; +export type VersioningBehavior = temporal.api.enums.v1.VersioningBehavior; + +// temporal.api.enums.v1.ResetReapplyExcludeType +export enum ResetReapplyExcludeType { + RESET_REAPPLY_EXCLUDE_TYPE_UNSPECIFIED = 0, + RESET_REAPPLY_EXCLUDE_TYPE_SIGNAL = 1, + RESET_REAPPLY_EXCLUDE_TYPE_UPDATE = 2, +} + +// temporal.api.enums.v1.ResetReapplyType +export enum ResetReapplyType { + RESET_REAPPLY_TYPE_UNSPECIFIED = 0, + RESET_REAPPLY_TYPE_SIGNAL = 1, + RESET_REAPPLY_TYPE_NONE = 2, + RESET_REAPPLY_TYPE_ALL_ELIGIBLE = 3, +} + +// api.workflow + +export type PendingActivityInfo = temporal.api.workflow.v1.IPendingActivityInfo; +export type PendingChildrenInfo = + temporal.api.workflow.v1.IPendingChildExecutionInfo; +export type PendingNexusInfo = + temporal.api.workflow.v1.IPendingNexusOperationInfo; +export type CallbackInfo = temporal.api.workflow.v1.ICallbackInfo; +export type Callback = temporal.api.common.v1.ICallback; +export type WorkflowExecutionConfig = + temporal.api.workflow.v1.IWorkflowExecutionConfig; +export type WorkflowExecutionInfo = + temporal.api.workflow.v1.IWorkflowExecutionInfo; +export type WorkflowVersionTimpstamp = + temporal.api.common.v1.IWorkerVersionStamp; +export type SearchAttribute = temporal.api.common.v1.ISearchAttributes; + +// api response +export type Payload = temporal.api.common.v1.IPayload; +export type Payloads = temporal.api.common.v1.IPayloads; +export type WorkflowExecutionInput = temporal.api.common.v1.IWorkflowExecution; +export type Memo = temporal.api.common.v1.IMemo; +export type Header = temporal.api.common.v1.IHeader; + +// api.taskqueue + +export type PollerInfo = temporal.api.taskqueue.v1.IPollerInfo; +export type TaskQueueStatus = temporal.api.taskqueue.v1.ITaskQueueStatus; +export type TaskQueueCompatibleVersionSet = + temporal.api.taskqueue.v1.ICompatibleVersionSet; +export type BuildIdReachability = temporal.api.taskqueue.v1.BuildIdReachability; + +// api.schedule + +export type ScheduleListEntry = temporal.api.schedule.v1.IScheduleListEntry; +export type ScheduleSpec = temporal.api.schedule.v1.IScheduleSpec; +export type ScheduleState = temporal.api.schedule.v1.IScheduleState; +export type SchedulePolicies = temporal.api.schedule.v1.ISchedulePolicies; + +export type CalendarSpec = temporal.api.schedule.v1.ICalendarSpec; +export type StructuredCalendarSpec = + temporal.api.schedule.v1.IStructuredCalendarSpec; +export type IntervalSpec = temporal.api.schedule.v1.IIntervalSpec; +export type RangeSpec = temporal.api.schedule.v1.IRange; + +export type ScheduleActionResult = + temporal.api.schedule.v1.IScheduleActionResult; + +// api.query +export type QueryResult = temporal.api.query.v1.IWorkflowQueryResult; + +// api.operatorservice +export type ListSearchAttributesResponse = + temporal.api.operatorservice.v1.IListSearchAttributesResponse; + +// api.batch +export type BatchCancelOperation = + temporal.api.batch.v1.IBatchOperationCancellation; +export type BatchTerminateOperation = + temporal.api.batch.v1.IBatchOperationTermination; + +// api.nexus +export type Endpoint = temporal.api.nexus.v1.IEndpoint; +export type EndpointSpec = temporal.api.nexus.v1.IEndpointSpec; + +// api.failure +export type Failure = temporal.api.failure.v1.IFailure; + +// google + +export type Timestamp = google.protobuf.ITimestamp; + +// extra APIs +export type SettingsResponse = { + Auth: { Enabled: boolean; Options: string[] }; + BannerText: string; + Codec: { + Endpoint: string; + PassAccessToken?: boolean; + IncludeCredentials?: boolean; + DefaultErrorMessage?: string; + DefaultErrorLink?: string; + }; + DefaultNamespace: string; + DisableWriteActions: boolean; + WorkflowTerminateDisabled: boolean; + WorkflowCancelDisabled: boolean; + WorkflowSignalDisabled: boolean; + WorkflowUpdateDisabled: boolean; + WorkflowResetDisabled: boolean; + BatchActionsDisabled: boolean; + StartWorkflowDisabled: boolean; + HideWorkflowQueryErrors: boolean; + RefreshWorkflowCountsDisabled: boolean; + ActivityCommandsDisabled: boolean; + ShowTemporalSystemNamespace: boolean; + NotifyOnNewVersion: boolean; + FeedbackURL: string; + Version: string; +}; diff --git a/src/lib/types/nexus.ts b/src/lib/types/nexus.ts new file mode 100644 index 000000000..558337906 --- /dev/null +++ b/src/lib/types/nexus.ts @@ -0,0 +1,26 @@ +import type { + CallbackInfo, + Endpoint, + EndpointSpec, + Callback as ICallback, +} from '$lib/types'; + +import type { EventLink } from './events'; + +export interface NexusEndpointSpec extends EndpointSpec { + descriptionString?: string; + allowedCallerNamespaces?: string[]; +} +export interface NexusEndpoint extends Endpoint { + asyncOperationId?: string; + state?: string; + spec?: NexusEndpointSpec; +} + +export interface Callback extends CallbackInfo { + blockedReason?: string; + callback?: CallbackWithLinks; +} +interface CallbackWithLinks extends ICallback { + links?: EventLink[]; +} diff --git a/src/lib/types/schedule.ts b/src/lib/types/schedule.ts new file mode 100644 index 000000000..025b21cbb --- /dev/null +++ b/src/lib/types/schedule.ts @@ -0,0 +1,83 @@ +import type { PayloadInputEncoding } from '$lib/components/payload-input-with-encoding.svelte'; +import type { SearchAttributeInput } from '$lib/stores/search-attributes'; +import type { + CalendarSpec, + DescribeScheduleResponse, + IntervalSpec, + RangeSpec, + Schedule, + ScheduleSpec, + StructuredCalendarSpec, +} from '$lib/types'; + +export type DescribeFullSchedule = DescribeScheduleResponse & { + schedule_id: string; + schedule?: Schedule; +}; + +export type FullSchedule = Schedule; +export type FullScheduleSpec = ScheduleSpec; +export type FullCalendarSpec = CalendarSpec; +export type StructuredCalendars = StructuredCalendarSpec[]; +export type StructuredCalendar = StructuredCalendarSpec; +export type ScheduleInterval = IntervalSpec; +export type ScheduleRange = RangeSpec; + +export type SchedulePreset = + | 'existing' + | 'interval' + | 'week' + | 'month' + | 'string'; + +export type ScheduleOffsetUnit = 'days' | 'hrs' | 'min' | 'sec'; + +export type ScheduleActionParameters = { + namespace: string; + name: string; + workflowType: string; + workflowId: string; + taskQueue: string; + input: string; + encoding: PayloadInputEncoding; + messageType?: string; + searchAttributes: SearchAttributeInput[]; + workflowSearchAttributes?: SearchAttributeInput[]; +}; + +export type ScheduleSpecParameters = { + dayOfWeek: string; + dayOfMonth: string; + month: string; + hour: string; + minute: string; + second: string; + phase: string; + cronString: string; + searchAttributes: SearchAttributeInput[]; + workflowSearchAttributes?: SearchAttributeInput[]; +}; + +// For UI Only +export type SchedulePresetsParameters = { + preset: SchedulePreset; + days: string; + daysOfWeek: string[]; + daysOfMonth: number[]; + months: string[]; +}; + +export type ScheduleParameters = ScheduleActionParameters & + ScheduleSpecParameters & + SchedulePresetsParameters; + +export type ScheduleStatus = 'Paused' | 'Running'; + +export type OverlapPolicy = + | 'Unspecified' + | 'Skip' + | 'BufferOne' + | 'BufferAll' + | 'CancelOther' + | 'TerminateOther' + | 'AllowAll'; diff --git a/src/lib/types/workflows.ts b/src/lib/types/workflows.ts new file mode 100644 index 000000000..02705732a --- /dev/null +++ b/src/lib/types/workflows.ts @@ -0,0 +1,231 @@ +import type { + Memo, + Payloads, + PendingWorkflowTaskInfo, + WorkflowExecutionStatus, + WorkflowVersionTimpstamp, +} from '$lib/types'; + +import type { VersioningInfo } from './deployments'; +import type { + Callbacks, + Payload, + PendingActivity, + PendingActivityInfo, + PendingChildren, + PendingNexusOperation, +} from './events'; +import type { Optional, Replace } from './global'; + +/** + * Replace Longs, ITimestamps, UInt8Array's etc. with their corresponding http values + */ + +type WorkflowExeuctionWithAssignedBuildId = + import('$lib/types').WorkflowExecutionInfo & { + assignedBuildId: string; + versioningInfo?: VersioningInfo; + }; + +export type WorkflowExecutionInfo = Replace< + WorkflowExeuctionWithAssignedBuildId, + { + status: WorkflowExecutionStatus | WorkflowStatus; + stateTransitionCount: string; + startTime: string; + closeTime: string; + executionTime: string; + historySizeBytes: string; + historyLength: string; + searchAttributes?: WorkflowSearchAttributes; + memo?: Memo; + } +>; + +export type ListWorkflowExecutionsResponse = Replace< + import('$lib/types').ListWorkflowExecutionsResponse, + Optional<{ executions: WorkflowExecutionInfo[] }> +>; + +export type CountWorkflowExecutionsResponse = { + count?: string; + groups?: { count: string; groupValues: Payloads }[]; +}; + +export type WorkflowExecutionConfig = Replace< + import('$lib/types').WorkflowExecutionConfig, + { defaultWorkflowTaskTimeout: Duration } +>; + +export type WorkflowInteractionDefinition = { + name?: string; + description?: string; +}; + +export type WorkflowMetadata = { + currentDetails?: string; + error?: Error; + definition?: { + type?: string; + queryDefinitions?: WorkflowInteractionDefinition[]; + signalDefinitions?: WorkflowInteractionDefinition[]; + updateDefinitions?: WorkflowInteractionDefinition[]; + }; +}; + +export type UserMetadata = { + summary?: Payload; + details?: Payload; +}; + +export type WorkflowExecutionConfigWithMetadata = WorkflowExecutionConfig & { + userMetadata?: UserMetadata; +}; + +export type WorkflowExtendedInfo = { + resetRunId?: string; + originalStartTime?: string; +}; + +export type WorkflowExecutionAPIResponse = Optional<{ + workflowExecutionInfo: WorkflowExecutionInfo; + pendingActivities: PendingActivityInfo[]; + pendingChildren: PendingChildren[]; + pendingNexusOperations: PendingNexusOperation[]; + executionConfig: WorkflowExecutionConfigWithMetadata; + callbacks: Callbacks; + pendingWorkflowTask: PendingWorkflowTaskInfo; + workflowExtendedInfo: WorkflowExtendedInfo; +}>; + +export type WorkflowStatus = + | 'Running' + | 'TimedOut' + | 'Completed' + | 'Failed' + | 'ContinuedAsNew' + | 'Canceled' + | 'Terminated' + | null; + +export type WorkflowType = string | null; + +export type FilterParameters = { + workflowId?: string; + workflowType?: string; + executionStatus?: WorkflowStatus; + timeRange?: Duration | string; + query?: string; +}; + +export type ArchiveFilterParameters = Omit & { + closeTime?: Duration | string; +}; + +export type WorkflowIdentifier = import('$lib/types').WorkflowExecutionInput; + +export const SEARCH_ATTRIBUTE_TYPE = { + BOOL: 'Bool', + DATETIME: 'Datetime', + DOUBLE: 'Double', + INT: 'Int', + KEYWORD: 'Keyword', + TEXT: 'Text', + KEYWORDLIST: 'KeywordList', + UNSPECIFIED: 'Unspecified', +} as const; + +type Keys = keyof typeof SEARCH_ATTRIBUTE_TYPE; +export type SearchAttributeType = (typeof SEARCH_ATTRIBUTE_TYPE)[Keys]; + +export type SearchAttributes = { + [k: string]: SearchAttributeType; +}; + +export type SearchAttributesResponse = { + customAttributes: Record; + systemAttributes: Record; + storageSchema: import('$lib/types').ListSearchAttributesResponse['storageSchema']; +}; + +export type WorkflowSearchAttributes = { + indexedFields?: Record; +}; + +export type DecodedWorkflowSearchAttributes = { + indexedFields?: Record; +}; + +export interface MostRecentWOrkflowVersionStamp + extends WorkflowVersionTimpstamp { + useVersioning?: boolean; +} + +export type WorkflowExecution = { + name: string; + id: string; + runId: string; + startTime: string; + endTime: string; + executionTime: string; + status: WorkflowStatus; + taskQueue?: string; + historyEvents: string; + historySizeBytes: string; + mostRecentWorkerVersionStamp?: MostRecentWOrkflowVersionStamp; + assignedBuildId?: string; + searchAttributes?: DecodedWorkflowSearchAttributes; + memo: Memo; + rootExecution?: WorkflowIdentifier; + pendingChildren: PendingChildren[]; + pendingNexusOperations: PendingNexusOperation[]; + pendingActivities: PendingActivity[]; + pendingWorkflowTask: PendingWorkflowTaskInfo; + stateTransitionCount: string; + parentNamespaceId?: string; + parent?: WorkflowIdentifier; + url: string; + isRunning: boolean; + defaultWorkflowTaskTimeout: Duration; + canBeTerminated: boolean; + callbacks: Callbacks; + versioningInfo?: VersioningInfo; + summary?: Payload; + details?: Payload; + workflowExtendedInfo: WorkflowExtendedInfo; +}; + +export type WorkflowTaskFailedCause = + | 'Unspecified' + | 'UnhandledCommand' + | 'BadScheduleActivityAttributes' + | 'BadRequestCancelActivityAttributes' + | 'BadStartTimerAttributes' + | 'BadCancelTimerAttributes' + | 'BadRecordMarkerAttributes' + | 'BadCompleteWorkflowExecutionAttributes' + | 'BadFailWorkflowExecutionAttributes' + | 'BadCancelWorkflowExecutionAttributes' // correct ? + | 'BadRequestCancelExternalAttributes' + | 'BadContinueAsNewAttributes' + | 'StartTimerDuplicateId' + | 'ResetStickyTaskQueue' + | 'WorkflowWorkerUnhandledFailure' + | 'WorkflowTaskHeartbeatError' + | 'BadSignalWorkflowExecutionAttributes' + | 'BadStartChildExecutionAttributes' + | 'ForceCloseCommand' + | 'FailoverCloseCommand' + | 'BadSignalInputSize' + | 'ResetWorkflow' + | 'BadBinary' + | 'ScheduleActivityDuplicateId' + | 'BadSearchAttributes' + | 'NonDeterministicError' + | 'BadModifyWorkflowPropertiesAttributes' + | 'PendingChildWorkflowsLimitExceeded' + | 'PendingActivitiesLimitExceeded' + | 'PendingSignalsLimitExceeded' + | 'PendingRequestCancelLimitExceeded' + | 'BadUpdateWorkflowExecutionMessage' + | 'UnhandledUpdate'; diff --git a/src/lib/utilities/activity-commands-enabled.test.ts b/src/lib/utilities/activity-commands-enabled.test.ts new file mode 100644 index 000000000..20242d19c --- /dev/null +++ b/src/lib/utilities/activity-commands-enabled.test.ts @@ -0,0 +1,72 @@ +import { describe, expect, test } from 'vitest'; + +import { activityCommandsEnabled } from './activity-commands-enabled'; + +describe('activityCommandsEnabled', () => { + const coreUser = { + namespaceWriteDisabled: (ns: string) => ns === 'ns-write-disabled', + isActivityCommandsDisabled: false, + }; + + test('returns true when global write actions, namespace write actions, and the feature flag is not enabled', () => { + expect( + activityCommandsEnabled( + { + disableWriteActions: false, + }, + coreUser, + 'ns-activity-commands', + ), + ).toBe(true); + }); + + describe('returns false', () => { + test('when write actions are disabled', () => { + expect( + activityCommandsEnabled( + { + disableWriteActions: true, + }, + coreUser, + 'ns-activity-commands', + ), + ).toBe(false); + }); + + test.skip('when the feature flag is enabled', () => { + expect( + activityCommandsEnabled( + { + disableWriteActions: false, + }, + { ...coreUser, isActivityCommandsDisabled: true }, + 'ns-activity-commands', + ), + ).toBe(false); + }); + + test('when write actions are disabled and the feature flag is enabled', () => { + expect( + activityCommandsEnabled( + { + disableWriteActions: true, + }, + { ...coreUser, isActivityCommandsDisabled: false }, + 'ns-activity-commands', + ), + ).toBe(false); + }); + + test('when namespace write actions are disabled', () => { + expect( + activityCommandsEnabled( + { + disableWriteActions: false, + }, + coreUser, + 'ns-write-disabled', + ), + ).toBe(false); + }); + }); +}); diff --git a/src/lib/utilities/activity-commands-enabled.ts b/src/lib/utilities/activity-commands-enabled.ts new file mode 100644 index 000000000..107997b7e --- /dev/null +++ b/src/lib/utilities/activity-commands-enabled.ts @@ -0,0 +1,20 @@ +import { get } from 'svelte/store'; + +import type { CoreUser } from '$lib/models/core-user'; +import { isCloud } from '$lib/stores/advanced-visibility'; +import type { Settings } from '$lib/types/global'; + +export const activityCommandsEnabled = ( + settings: Settings, + coreUser: CoreUser, + namespace: string, +): boolean => { + // TODO: Remove isCloud check and add back isActivityCommandsDisabled check when feature is ready for Cloud + return ( + !settings.disableWriteActions && + !coreUser.namespaceWriteDisabled(namespace) && + // !coreUser.isActivityCommandsDisabled && + !settings.activityCommandsDisabled && + !get(isCloud) + ); +}; diff --git a/src/lib/utilities/advanced-visibility-enabled.test.ts b/src/lib/utilities/advanced-visibility-enabled.test.ts index 300d5c903..b1f488197 100644 --- a/src/lib/utilities/advanced-visibility-enabled.test.ts +++ b/src/lib/utilities/advanced-visibility-enabled.test.ts @@ -1,20 +1,32 @@ import { describe, expect, it } from 'vitest'; + import { advancedVisibilityEnabled } from './advanced-visibility-enabled'; describe('advancedVisbilityEnabled', () => { it('returns true when "elasticsearch" is included in the cluster\'s visbilityStores', () => { expect( - advancedVisibilityEnabled({ visibilityStore: 'elasticsearch,mysql' }), + advancedVisibilityEnabled( + { visibilityStore: 'elasticsearch,mysql' }, + '1.18', + ), ).toBe(true); }); it('returns true when "elasticsearch" is the only value in the cluster\'s visbilityStores', () => { expect( - advancedVisibilityEnabled({ visibilityStore: 'elasticsearch' }), + advancedVisibilityEnabled({ visibilityStore: 'elasticsearch' }, '1.18'), ).toBe(true); }); it('returns false when "elasticsearch" is not included in the cluster\'s visbilityStores', () => { - expect(advancedVisibilityEnabled({ visibilityStore: 'mysql' })).toBe(false); + expect( + advancedVisibilityEnabled({ visibilityStore: 'mysql' }, '1.19'), + ).toBe(false); + }); + + it('returns true when "elasticsearch" is not included in the cluster\'s visbilityStores but server version is >= 1.20', () => { + expect( + advancedVisibilityEnabled({ visibilityStore: 'mysql' }, '1.20'), + ).toBe(true); }); }); diff --git a/src/lib/utilities/advanced-visibility-enabled.ts b/src/lib/utilities/advanced-visibility-enabled.ts index 7fe117725..f7d3e8d8a 100644 --- a/src/lib/utilities/advanced-visibility-enabled.ts +++ b/src/lib/utilities/advanced-visibility-enabled.ts @@ -1,3 +1,19 @@ -export const advancedVisibilityEnabled = (cluster: ClusterInformation) => { - return cluster.visibilityStore.includes('elasticsearch'); +import type { ClusterInformation } from '$lib/types/global'; + +import { isVersionNewer } from './version-check'; + +export const advancedVisibilityEnabled = ( + cluster: ClusterInformation, + version: string, +) => { + return ( + cluster?.visibilityStore?.includes('elasticsearch') || + isVersionNewer(version, '1.19') + ); +}; + +export const advancedVisibilityEnabledWithOrderBy = ( + cluster: ClusterInformation, +) => { + return cluster?.visibilityStore?.includes('elasticsearch'); }; diff --git a/src/lib/utilities/api-error-handler.ts b/src/lib/utilities/api-error-handler.ts new file mode 100644 index 000000000..926b8365e --- /dev/null +++ b/src/lib/utilities/api-error-handler.ts @@ -0,0 +1,83 @@ +import { isNetworkError } from './is-network-error'; + +export interface ApiError extends Error { + statusCode?: number; + statusText?: string; + userMessage: string; + isRetryable: boolean; + isTemporary: boolean; +} + +export const createApiError = ( + error: unknown, + operation: string = 'operation', +): ApiError => { + const baseMessage = `Failed to ${operation}`; + + if (isNetworkError(error)) { + return { + ...error, + name: 'ApiError', + userMessage: getNetworkErrorMessage(error, operation), + isRetryable: isRetryableError(error.statusCode), + isTemporary: isTemporaryError(error.statusCode), + } as ApiError; + } + + if (error instanceof Error) { + return { + ...error, + name: 'ApiError', + userMessage: `${baseMessage}: ${error.message}`, + isRetryable: false, + isTemporary: false, + } as ApiError; + } + + const unknownError = new Error(`${baseMessage}: Unknown error`) as ApiError; + unknownError.name = 'ApiError'; + unknownError.userMessage = `${baseMessage}: An unexpected error occurred`; + unknownError.isRetryable = false; + unknownError.isTemporary = false; + + return unknownError; +}; + +const getNetworkErrorMessage = ( + error: { statusCode: number; statusText: string }, + operation: string, +): string => { + switch (error.statusCode) { + case 400: + return 'Invalid data provided. Please check your entries and try again.'; + case 401: + return 'Authentication required. Please log in and try again.'; + case 403: + return `Permission denied. You may not have access to perform this ${operation}.`; + case 404: + return 'Resource not found. It may have been deleted or moved.'; + case 409: + return 'Conflict detected. The resource may have been modified by another user.'; + case 422: + return 'Invalid data format. Please check your entries and try again.'; + case 429: + return 'Too many requests. Please wait a moment and try again.'; + case 500: + case 502: + case 503: + case 504: + return 'Server error occurred. Please try again in a few moments.'; + default: + return `Network error (${error.statusCode}): ${error.statusText}`; + } +}; + +const isRetryableError = (statusCode?: number): boolean => { + if (!statusCode) return false; + return [408, 429, 500, 502, 503, 504].includes(statusCode); +}; + +const isTemporaryError = (statusCode?: number): boolean => { + if (!statusCode) return false; + return [429, 500, 502, 503, 504].includes(statusCode); +}; diff --git a/src/lib/utilities/atob.test.ts b/src/lib/utilities/atob.test.ts index 396ad80f3..320a8959f 100644 --- a/src/lib/utilities/atob.test.ts +++ b/src/lib/utilities/atob.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { base64DecodeUnicode, atob } from './atob'; + +import { atob, base64DecodeUnicode } from './atob'; describe('base64DecodeUnicode', () => { it('should decode unicode characters', () => { @@ -34,6 +35,13 @@ describe('atob', () => { expect(res).toEqual('"rainbow statuses df2f81 Terminated"'); }); + it('should decode unicode within uft-8 characters using the fallback', () => { + const res = atob( + 'eyJJbnB1dCI6eyAiUmVzdWx0IjoiXG5cdTAwMWRcblx0MzkwOTQ1MTEzXHUwMDEyXHQzOTA5MzYzMjFcdTAwMWFcdTAwMDVcdTAwMDgkXHUwMDEwXHUwMDA3In0sIk91dHB1dCI6eyJSZXN1bHQiOiJ0cnVlIn19', + ); + expect(res).toContain('Result'); + }); + it('should become a no-op if browser is set to false', () => { const res = atob('InJhaW5ib3cgc3RhdHVzZXMgZGYyZjgxIFRlcm1pbmF0ZWQi', false); expect(res).toEqual('InJhaW5ib3cgc3RhdHVzZXMgZGYyZjgxIFRlcm1pbmF0ZWQi'); diff --git a/src/lib/utilities/atob.ts b/src/lib/utilities/atob.ts index f8a735c36..b943826a4 100644 --- a/src/lib/utilities/atob.ts +++ b/src/lib/utilities/atob.ts @@ -1,18 +1,34 @@ -import { browser } from '$app/env'; +import { BROWSER } from 'esm-env'; + +import { parseWithBigInt, stringifyWithBigInt } from './parse-with-big-int'; + +function createAtobStringForUnicode(str: string) { + return window + .atob(str) + .split('') + .map(function (c) { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }) + .join(''); +} export function base64DecodeUnicode(str: string): string { - return decodeURIComponent( - window - .atob(str) - .split('') - .map(function (c) { - return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); - }) - .join(''), - ); + return decodeURIComponent(createAtobStringForUnicode(str)); +} + +function base64DecodeUnicodeFallback(str: string): string { + try { + return stringifyWithBigInt(parseWithBigInt(window.atob(str))); + } catch (e) { + return str; + } } -export const atob = (str: string, isBrowser = browser): string => { +export const atob = (str: string, isBrowser = BROWSER): string => { if (!isBrowser) return str; - return base64DecodeUnicode(str); + try { + return base64DecodeUnicode(str); + } catch (e) { + return base64DecodeUnicodeFallback(str); + } }; diff --git a/src/lib/utilities/auth-user-cookie.ts b/src/lib/utilities/auth-user-cookie.ts index 3833a20ec..651d9952c 100644 --- a/src/lib/utilities/auth-user-cookie.ts +++ b/src/lib/utilities/auth-user-cookie.ts @@ -1,7 +1,10 @@ -import { browser } from '$app/env'; -import { parseWithBigInt } from './parse-with-big-int'; +import { BROWSER } from 'esm-env'; + +import type { User } from '$lib/types/global'; import { atob } from '$lib/utilities/atob'; +import { parseWithBigInt } from './parse-with-big-int'; + type UserResponse = { AccessToken: string; IDToken: string; @@ -12,7 +15,7 @@ type UserResponse = { const cookieName = 'user'; -export const getAuthUserCookie = (isBrowser = browser): User => { +export const getAuthUserCookie = (isBrowser = BROWSER): User => { if (!isBrowser) return {}; const cookies = document.cookie.split(';'); @@ -48,7 +51,7 @@ export const getAuthUserCookie = (isBrowser = browser): User => { return {}; }; -export const cleanAuthUserCookie = (isBrowser = browser) => { +export const cleanAuthUserCookie = (isBrowser = BROWSER) => { if (!isBrowser) return; const cookies = document.cookie.split(';'); diff --git a/src/lib/utilities/btoa.test.ts b/src/lib/utilities/btoa.test.ts new file mode 100644 index 000000000..60c59cbd4 --- /dev/null +++ b/src/lib/utilities/btoa.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from 'vitest'; + +import { base64EncodeUnicode, btoa } from './btoa'; + +describe('base64DecodeUnicode', () => { + it('should decode unicode characters', () => { + const res = base64EncodeUnicode( + '{"Hey":"表ポあA鷗ŒéB逍Üߪąñ丂㐀𠀀впавпавцу4 цупку ・:*:・゜’( ☻ ω ☻ )。 💁 🙅 🙆 🙋 🙎 🙍 ا لحدود أي بعد, معاملة بولندا، الإطلاق ᚓᚐᚋᚒᚄ v̟̜̘̦͟o̶̙̰̠kè͚̮̺̪̹̱̤ ̖t̝͕̳̣̻̪͞h̼͓̲̦̳̘̲e͇̣̰̦̬͎ ̢̼̻̱̘h͚͎͙̜̣̲ͅi̦̲̣̰̤v̻͍ ","At":"2022-05-10T10:26:18.336078441-04:00"}', + ); + expect(res).toEqual( + 'eyJIZXkiOiLooajjg53jgYJB6beXxZLDqe+8oumAjcOcw5/CqsSFw7HkuILjkIDwoICA0LLQv9Cw0LLQv9Cw0LLRhtGDNCDRhtGD0L/QutGDIOODuzoqOuODu+OCnOKAmSgg4pi7IM+JIOKYuyAp44CCIPCfkoEg8J+ZhSDwn5mGIPCfmYsg8J+ZjiDwn5mNINinINmE2K3Yr9mI2K8g2KPZiiDYqNi52K8sINmF2LnYp9mF2YTYqSDYqNmI2YTZhtiv2KfYjCDYp9mE2KXYt9mE2KfZgiDhmpPhmpDhmovhmpLhmoQgdsyfzJzMmMymzZ9vzLbMmcywzKBrw6jNmsyuzLrMqsy5zLHMpCDMlnTMnc2VzLPMo8y7zKrNnmjMvM2TzLLMpsyzzJjMsmXNh8yjzLDMpsyszY4gzKLMvMy7zLHMmGjNms2OzZnMnMyjzLLNhWnMpsyyzKPMsMykdsy7zY0gIiwiQXQiOiIyMDIyLTA1LTEwVDEwOjI2OjE4LjMzNjA3ODQ0MS0wNDowMCJ9', + ); + }); + + it('should decode english characters', () => { + const res = base64EncodeUnicode('"rainbow statuses df2f81 Terminated"'); + expect(res).toEqual('InJhaW5ib3cgc3RhdHVzZXMgZGYyZjgxIFRlcm1pbmF0ZWQi'); + }); +}); + +describe('atob', () => { + it('should decode unicode characters', () => { + const res = btoa( + '{"Hey":"表ポあA鷗ŒéB逍Üߪąñ丂㐀𠀀впавпавцу4 цупку ・:*:・゜’( ☻ ω ☻ )。 💁 🙅 🙆 🙋 🙎 🙍 ا لحدود أي بعد, معاملة بولندا، الإطلاق ᚓᚐᚋᚒᚄ v̟̜̘̦͟o̶̙̰̠kè͚̮̺̪̹̱̤ ̖t̝͕̳̣̻̪͞h̼͓̲̦̳̘̲e͇̣̰̦̬͎ ̢̼̻̱̘h͚͎͙̜̣̲ͅi̦̲̣̰̤v̻͍ ","At":"2022-05-10T10:26:18.336078441-04:00"}', + ); + expect(res).toEqual( + 'eyJIZXkiOiLooajjg53jgYJB6beXxZLDqe+8oumAjcOcw5/CqsSFw7HkuILjkIDwoICA0LLQv9Cw0LLQv9Cw0LLRhtGDNCDRhtGD0L/QutGDIOODuzoqOuODu+OCnOKAmSgg4pi7IM+JIOKYuyAp44CCIPCfkoEg8J+ZhSDwn5mGIPCfmYsg8J+ZjiDwn5mNINinINmE2K3Yr9mI2K8g2KPZiiDYqNi52K8sINmF2LnYp9mF2YTYqSDYqNmI2YTZhtiv2KfYjCDYp9mE2KXYt9mE2KfZgiDhmpPhmpDhmovhmpLhmoQgdsyfzJzMmMymzZ9vzLbMmcywzKBrw6jNmsyuzLrMqsy5zLHMpCDMlnTMnc2VzLPMo8y7zKrNnmjMvM2TzLLMpsyzzJjMsmXNh8yjzLDMpsyszY4gzKLMvMy7zLHMmGjNms2OzZnMnMyjzLLNhWnMpsyyzKPMsMykdsy7zY0gIiwiQXQiOiIyMDIyLTA1LTEwVDEwOjI2OjE4LjMzNjA3ODQ0MS0wNDowMCJ9', + ); + }); + + it('should decode english characters', () => { + const res = btoa('"rainbow statuses df2f81 Terminated"'); + expect(res).toEqual('InJhaW5ib3cgc3RhdHVzZXMgZGYyZjgxIFRlcm1pbmF0ZWQi'); + }); + + it('should become a no-op if browser is set to false', () => { + const res = btoa('InJhaW5ib3cgc3RhdHVzZXMgZGYyZjgxIFRlcm1pbmF0ZWQi', false); + expect(res).toEqual('InJhaW5ib3cgc3RhdHVzZXMgZGYyZjgxIFRlcm1pbmF0ZWQi'); + }); +}); diff --git a/src/lib/utilities/btoa.ts b/src/lib/utilities/btoa.ts new file mode 100644 index 000000000..2f7c2f429 --- /dev/null +++ b/src/lib/utilities/btoa.ts @@ -0,0 +1,14 @@ +import { BROWSER } from 'esm-env'; + +export const base64EncodeUnicode = (str: string) => { + return window.btoa( + encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (_, p1) => + String.fromCharCode(parseInt(p1, 16)), + ), + ); +}; + +export const btoa = (str: string, isBrowser = BROWSER): string => { + if (!isBrowser) return str; + return base64EncodeUnicode(str); +}; diff --git a/src/lib/utilities/bulk-actions-enabled.test.ts b/src/lib/utilities/bulk-actions-enabled.test.ts new file mode 100644 index 000000000..ed6feaba2 --- /dev/null +++ b/src/lib/utilities/bulk-actions-enabled.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, test } from 'vitest'; + +import { bulkActionsEnabled } from './bulk-actions-enabled'; + +describe('bulkActionsEnabled', () => { + test('returns true when all settings flags are false', () => { + expect( + bulkActionsEnabled({ + disableWriteActions: false, + batchActionsDisabled: false, + workflowTerminateDisabled: false, + workflowCancelDisabled: false, + }), + ).toBe(true); + }); + + describe('returns false', () => { + test('when `disableWriteActions` is `true`', () => { + expect( + bulkActionsEnabled({ + disableWriteActions: true, + }), + ).toBe(false); + }); + + test('when `disableWriteActions` is `false`, but `batchActionsDisabled` is `true`', () => { + expect( + bulkActionsEnabled({ + disableWriteActions: false, + batchActionsDisabled: true, + }), + ).toBe(false); + }); + + test('when `disableWriteActions` and `batchActionsDisabled` are both `false`, but `worklowCancelDisabled` and `workflowTerminateDisabled` are both true', () => { + expect( + bulkActionsEnabled({ + disableWriteActions: false, + batchActionsDisabled: false, + workflowCancelDisabled: true, + workflowTerminateDisabled: true, + }), + ).toBe(false); + }); + }); +}); diff --git a/src/lib/utilities/bulk-actions-enabled.ts b/src/lib/utilities/bulk-actions-enabled.ts new file mode 100644 index 000000000..654cf34ec --- /dev/null +++ b/src/lib/utilities/bulk-actions-enabled.ts @@ -0,0 +1,16 @@ +import type { Settings } from '$lib/types/global'; + +const ALLOWED_BULK_ACTIONS: (keyof Pick< + Settings, + | 'workflowSignalDisabled' + | 'workflowCancelDisabled' + | 'workflowResetDisabled' + | 'workflowTerminateDisabled' +>)[] = ['workflowCancelDisabled', 'workflowTerminateDisabled']; + +export const bulkActionsEnabled = (settings: Settings) => { + if (settings.disableWriteActions) return false; + if (settings.batchActionsDisabled) return false; + + return ALLOWED_BULK_ACTIONS.some((action) => !settings[action]); +}; diff --git a/src/lib/utilities/calendar.ts b/src/lib/utilities/calendar.ts index 15d811312..65c54109b 100644 --- a/src/lib/utilities/calendar.ts +++ b/src/lib/utilities/calendar.ts @@ -30,28 +30,29 @@ export const weekDays = [ ]; const monthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; -const isLeapYear = (year) => year % 4 === 0; +const isLeapYear = (year: number) => year % 4 === 0; const getEmptyRows: () => number[] = () => { // Max days = 7 days * 6 week rows = 42 const rows = Array.from({ length: 42 }); return rows.map(() => 0); }; -const getMonthDays = (index, year) => { +const getMonthDays = (index: number, year: number) => { return index !== 1 ? monthDays[index] : isLeapYear(year) ? 29 : 28; }; -const getMonthStats = (monthIndex, year) => { +const getMonthStats = (monthIndex: number, year: number) => { const today = new Date(year, monthIndex, 1); const index = today.getMonth(); return { - name: index[index], + name: monthNames[index], days: getMonthDays(index, year), }; }; -export const getMonthName = (index) => monthNames[index]; +export const getMonthName = (index: number) => + monthNames.filter((m) => m.value !== '*')[index]; -export const getDateRows = (monthIndex, year) => { +export const getDateRows = (monthIndex: number, year: number) => { const { days } = getMonthStats(monthIndex, year); const rows = getEmptyRows(); const startIndex = new Date(year, monthIndex, 1).getDay(); diff --git a/src/lib/utilities/cancel-in-progress.test.ts b/src/lib/utilities/cancel-in-progress.test.ts new file mode 100644 index 000000000..00d249174 --- /dev/null +++ b/src/lib/utilities/cancel-in-progress.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from 'vitest'; + +import { isCancelInProgress } from './cancel-in-progress'; + +import canceledEvents from '$fixtures/events.canceled.json'; +import failedEvents from '$fixtures/events.failed.json'; + +describe('isCancelInProgress', () => { + it('should return true if running and not updating and CancelRequested event', () => { + expect(isCancelInProgress('Running', canceledEvents)).toBe(true); + }); + + it('should return false if running and not updating and no CancelRequested event', () => { + expect(isCancelInProgress('Running', failedEvents)).toBe(false); + }); + + it('should return false if completed and updating and no CancelRequested event', () => { + expect(isCancelInProgress('Completed', canceledEvents)).toBe(false); + }); + + it('should return false if no history', () => { + expect(isCancelInProgress('Running', [])).toBe(false); + }); +}); diff --git a/src/lib/utilities/cancel-in-progress.ts b/src/lib/utilities/cancel-in-progress.ts new file mode 100644 index 000000000..2cf6457b7 --- /dev/null +++ b/src/lib/utilities/cancel-in-progress.ts @@ -0,0 +1,13 @@ +import type { WorkflowEvents } from '$lib/types/events'; +import type { WorkflowStatus } from '$lib/types/workflows'; + +export const isCancelInProgress = ( + status: WorkflowStatus, + eventHistory: WorkflowEvents, +) => { + const isRunning = status === 'Running'; + const workflowCancelRequested = eventHistory?.some( + (event) => event?.eventType === 'WorkflowExecutionCancelRequested', + ); + return isRunning && workflowCancelRequested; +}; diff --git a/src/lib/utilities/dark-mode/dark-mode.svelte b/src/lib/utilities/dark-mode/dark-mode.svelte new file mode 100644 index 000000000..b90106b6c --- /dev/null +++ b/src/lib/utilities/dark-mode/dark-mode.svelte @@ -0,0 +1,6 @@ + + + + diff --git a/src/lib/utilities/dark-mode/dark-mode.test.ts b/src/lib/utilities/dark-mode/dark-mode.test.ts new file mode 100644 index 000000000..951baf8a5 --- /dev/null +++ b/src/lib/utilities/dark-mode/dark-mode.test.ts @@ -0,0 +1,88 @@ +import { get } from 'svelte/store'; + +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { + darkMode, + getNextDarkModePreference, + useDarkMode, + useDarkModePreference, +} from './dark-mode'; + +describe('dark-mode utilities', () => { + let matchMediaMock; + + beforeEach(() => { + matchMediaMock = vi.fn(); + Object.defineProperty(window, 'matchMedia', { + writable: true, + value: matchMediaMock, + }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('useDarkMode', () => { + it('should return true if prefers-color-scheme is dark and preference is system', () => { + matchMediaMock.mockReturnValue({ matches: true }); // prefers dark + useDarkModePreference.set('system'); + const value = get(useDarkMode); + expect(value).toBe(true); + }); + + it('should return false if prefers-color-scheme is not dark and preference is system', () => { + matchMediaMock.mockReturnValue({ matches: false }); + useDarkModePreference.set('system'); + const value = get(useDarkMode); + expect(value).toBe(false); + }); + + it('should return the user preference if it is set', () => { + useDarkModePreference.set(true); + const value1 = get(useDarkMode); + expect(value1).toBe(true); + + useDarkModePreference.set(false); + const value2 = get(useDarkMode); + expect(value2).toBe(false); + }); + }); + + describe('getNextDarkModePreference', () => { + it('should return true if the current value is system', () => { + expect(getNextDarkModePreference('system')).toBe(true); + }); + + it('should return false if the current value is true', () => { + expect(getNextDarkModePreference(true)).toBe(false); + }); + + it('should return system if the current value is false', () => { + expect(getNextDarkModePreference(false)).toBe('system'); + }); + }); + + describe('darkMode', () => { + it('should set data-theme to "dark" when dark mode is enabled', async () => { + const node = document.createElement('div'); + useDarkModePreference.set(true); + + darkMode(node); + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(node.dataset.theme).toBe('dark'); + }); + + it('should set data-theme to "light" when dark mode is disabled', async () => { + const node = document.createElement('div'); + useDarkModePreference.set(false); + + darkMode(node); + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(node.dataset.theme).toBe('light'); + }); + }); +}); diff --git a/src/lib/utilities/dark-mode/dark-mode.ts b/src/lib/utilities/dark-mode/dark-mode.ts new file mode 100644 index 000000000..9f5b91c6b --- /dev/null +++ b/src/lib/utilities/dark-mode/dark-mode.ts @@ -0,0 +1,37 @@ +import { derived } from 'svelte/store'; + +import { persistStore } from '$lib/stores/persist-store'; + +export type DarkModePreference = boolean | 'system'; + +export const useDarkModePreference = persistStore( + 'dark mode', + !!import.meta.env.VITE_DARK_MODE, + true, +); + +export const useDarkMode = derived( + useDarkModePreference, + ($useDarkModePreference) => { + if ($useDarkModePreference == 'system') { + return ( + window.matchMedia?.('(prefers-color-scheme: dark)')?.matches ?? false + ); + } else { + return $useDarkModePreference; + } + }, +); + +export const getNextDarkModePreference = (value: DarkModePreference) => + value == 'system' ? true : value == true ? false : 'system'; + +export const darkMode = (node: HTMLElement) => { + useDarkMode.subscribe((value) => { + if (value) { + node.dataset.theme = 'dark'; + } else { + node.dataset.theme = 'light'; + } + }); +}; diff --git a/src/lib/utilities/dark-mode/index.ts b/src/lib/utilities/dark-mode/index.ts new file mode 100644 index 000000000..49e5192a9 --- /dev/null +++ b/src/lib/utilities/dark-mode/index.ts @@ -0,0 +1,10 @@ +import DarkMode from './dark-mode.svelte'; + +export default DarkMode; +export { + useDarkMode, + useDarkModePreference, + getNextDarkModePreference, +} from './dark-mode'; + +export type { DarkModePreference } from './dark-mode'; diff --git a/src/lib/utilities/data-converter-websocket.ts b/src/lib/utilities/data-converter-websocket.ts deleted file mode 100644 index 624089cd5..000000000 --- a/src/lib/utilities/data-converter-websocket.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { get } from 'svelte/store'; -import WebSocketAsPromised from 'websocket-as-promised'; -import type Options from 'websocket-as-promised/types/options'; -import { - dataConverterPort, - setLastDataConverterFailure, -} from '../stores/data-converter-config'; -import { parseWithBigInt, stringifyWithBigInt } from './parse-with-big-int'; - -export interface DataConverterWebsocketInterface { - hasWebsocket: boolean; - websocket: WebSocketAsPromised; - closeSocket: () => Promise; -} - -export const createWebsocket = ( - port: string | null, - extraParams?: Options, -): DataConverterWebsocketInterface => { - if (!port) { - return { - hasWebsocket: false, - websocket: null, - closeSocket: function () { - return null; - }, - }; - } - - try { - sock = new WebSocketAsPromised(`ws://localhost:${port}/`, { - packMessage: (data) => stringifyWithBigInt(data), - unpackMessage: (data) => parseWithBigInt(data as string), - attachRequestId: (data, requestId) => - Object.assign({ requestId: requestId }, data), - extractRequestId: (data) => data && data.requestId, - - ...extraParams, - }); - sock.onError.addListener((event: unknown) => { - console.error(`Websocket connection error: ${event}`); - }); - } catch (err) { - setLastDataConverterFailure(`Error creating websocket: ${err}`); - } - - sock.open(); - - return { - hasWebsocket: true, - websocket: sock, - closeSocket: function (): Promise { - return sock.close(); - }, - }; -}; - -let sock = null; -const port = get(dataConverterPort) ?? null; - -export const dataConverterWebsocket = createWebsocket(port); diff --git a/src/lib/utilities/decode-payload-test-fixtures.ts b/src/lib/utilities/decode-payload-test-fixtures.ts index 5e27dad10..0c1b205df 100644 --- a/src/lib/utilities/decode-payload-test-fixtures.ts +++ b/src/lib/utilities/decode-payload-test-fixtures.ts @@ -14,10 +14,9 @@ export const workflowStartedEvent = { payloads: [ { metadata: { - encoding: 'YmluYXJ5L2VuY3J5cHRlZA==', - 'encryption-key-id': '', + encoding: 'anNvbi9wbGFpbg==', }, - data: 'jfgr8zMj+jHMPeSgxnMnUw//hBOox6L38f52OX/ftDAoJkUi/zdtl7950O6wJG68GdX0WtwmV48GaGkC04pYyCjr5E2MSi/pG/SZbIRHoqs3GmWdvcBjQIAzTElk8aqP3r0ttRynyyLe', + data: 'InRlc3RAdGVzdC5jb20i', }, ], }, @@ -51,12 +50,7 @@ export const workflowStartedEvent = { prevAutoResetPoints: null, header: { fields: { - encryption: { - metadata: { - encoding: 'YmluYXJ5L251bGw=', - }, - data: null, - }, + encryption: null, }, }, }; @@ -68,7 +62,7 @@ export const dataConvertedWorkflowStartedEvent = { parentWorkflowExecution: null, parentInitiatedEventId: '0', taskQueue: { name: 'background-checks-main', kind: 'Normal' }, - input: { payloads: [{ Transformer: 'OptimusPrime' }] }, + input: { payloads: ['test@test.com'] }, workflowExecutionTimeout: '0s', workflowRunTimeout: '0s', workflowTaskTimeout: '10s', @@ -89,13 +83,7 @@ export const dataConvertedWorkflowStartedEvent = { prevAutoResetPoints: null, header: { fields: { - encryption: { - metadata: { - encoding: 'YmluYXJ5L251bGw=', - encodingDecoded: 'binary/null', - }, - data: null, - }, + encryption: null, }, }, }; @@ -111,10 +99,9 @@ export const dataConvertedFailureWorkflowStartedEvent = { payloads: [ { metadata: { - encoding: 'YmluYXJ5L2VuY3J5cHRlZA==', - 'encryption-key-id': '', + encoding: 'anNvbi9wbGFpbg==', }, - data: 'jfgr8zMj+jHMPeSgxnMnUw//hBOox6L38f52OX/ftDAoJkUi/zdtl7950O6wJG68GdX0WtwmV48GaGkC04pYyCjr5E2MSi/pG/SZbIRHoqs3GmWdvcBjQIAzTElk8aqP3r0ttRynyyLe', + data: 'InRlc3RAdGVzdC5jb20i', }, ], }, @@ -138,13 +125,7 @@ export const dataConvertedFailureWorkflowStartedEvent = { prevAutoResetPoints: null, header: { fields: { - encryption: { - metadata: { - encoding: 'YmluYXJ5L251bGw=', - encodingDecoded: 'binary/null', - }, - data: null, - }, + encryption: null, }, }, }; @@ -156,18 +137,7 @@ export const noRemoteDataConverterWorkflowStartedEvent = { parentWorkflowExecution: null, parentInitiatedEventId: '0', taskQueue: { name: 'background-checks-main', kind: 'Normal' }, - input: { - payloads: [ - { - metadata: { - encoding: 'YmluYXJ5L2VuY3J5cHRlZA==', - 'encryption-key-id': '', - encodingDecoded: 'binary/encrypted', - }, - data: 'jfgr8zMj+jHMPeSgxnMnUw//hBOox6L38f52OX/ftDAoJkUi/zdtl7950O6wJG68GdX0WtwmV48GaGkC04pYyCjr5E2MSi/pG/SZbIRHoqs3GmWdvcBjQIAzTElk8aqP3r0ttRynyyLe', - }, - ], - }, + input: { payloads: ['test@test.com'] }, workflowExecutionTimeout: '0s', workflowRunTimeout: '0s', workflowTaskTimeout: '10s', @@ -188,13 +158,7 @@ export const noRemoteDataConverterWorkflowStartedEvent = { prevAutoResetPoints: null, header: { fields: { - encryption: { - metadata: { - encoding: 'YmluYXJ5L251bGw=', - encodingDecoded: 'binary/null', - }, - data: null, - }, + encryption: null, }, }, }; @@ -215,10 +179,10 @@ export const workflowStartedHistoryEvent = { payloads: [ { metadata: { - encoding: 'YmluYXJ5L2VuY3J5cHRlZA==', + encoding: 'anNvbi9wbGFpbg==', 'encryption-key-id': '', }, - data: 'jfgr8zMj+jHMPeSgxnMnUw//hBOox6L38f52OX/ftDAoJkUi/zdtl7950O6wJG68GdX0WtwmV48GaGkC04pYyCjr5E2MSi/pG/SZbIRHoqs3GmWdvcBjQIAzTElk8aqP3r0ttRynyyLe', + data: 'InRlc3RAdGVzdC5jb20i', }, ], }, @@ -252,13 +216,121 @@ export const workflowStartedHistoryEvent = { prevAutoResetPoints: null, header: { fields: { - encryption: { + encryption: null, + }, + }, + }, +}; + +export const getTestPayloadEvent = () => ({ + type: 'workflowExecutionStartedEventAttributes', + workflowType: { + name: 'BackgroundCheck', + }, + parentWorkflowNamespace: '', + parentWorkflowExecution: null, + parentInitiatedEventId: '0', + taskQueue: { + name: 'background-checks-main', + kind: 'Normal', + }, + input: { + payloads: [ + { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + }, + data: 'InRlc3RAdGVzdC5jb20i', + }, + ], + }, + details: { + detail1: { + payloads: [ + { metadata: { - encoding: 'YmluYXJ5L251bGw=', + encoding: 'anNvbi9wbGFpbg==', }, - data: null, + data: 'eyAidGVzdCI6ICJkZXRhaWwiIH0=', + }, + ], + }, + }, + encodedAttributes: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + }, + data: 'ImEgdGVzdCBhdHRyaWJ1dGUi', + }, + workflowExecutionTimeout: '0s', + workflowRunTimeout: '0s', + workflowTaskTimeout: '10s', + continuedExecutionRunId: '', + initiator: 'Unspecified', + continuedFailure: null, + lastCompletionResult: null, + originalExecutionRunId: 'b4351cb3-f54f-4ed4-be5a-b13ef429b861', + identity: '1@ac0cd56257aa@', + firstExecutionRunId: 'b4351cb3-f54f-4ed4-be5a-b13ef429b861', + retryPolicy: null, + attempt: 1, + workflowExecutionExpirationTime: null, + cronSchedule: '', + firstWorkflowTaskBackoff: '0s', + memo: null, + prevAutoResetPoints: null, +}); + +export const getTestPayloadEventWithNullEncodedAttributes = () => ({ + type: 'workflowExecutionStartedEventAttributes', + workflowType: { + name: 'BackgroundCheck', + }, + parentWorkflowNamespace: '', + parentWorkflowExecution: null, + parentInitiatedEventId: '0', + taskQueue: { + name: 'background-checks-main', + kind: 'Normal', + }, + input: { + payloads: [ + { + metadata: { + encoding: 'anNvbi9wbGFpbg==', }, + data: 'InRlc3RAdGVzdC5jb20i', }, + ], + }, + details: { + detail1: { + payloads: [ + { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + }, + data: 'eyAidGVzdCI6ICJkZXRhaWwiIH0=', + }, + ], }, }, -}; + encodedAttributes: null, + workflowExecutionTimeout: '0s', + workflowRunTimeout: '0s', + workflowTaskTimeout: '10s', + continuedExecutionRunId: '', + initiator: 'Unspecified', + continuedFailure: null, + lastCompletionResult: null, + originalExecutionRunId: 'b4351cb3-f54f-4ed4-be5a-b13ef429b861', + identity: '1@ac0cd56257aa@', + firstExecutionRunId: 'b4351cb3-f54f-4ed4-be5a-b13ef429b861', + retryPolicy: null, + attempt: 1, + workflowExecutionExpirationTime: null, + cronSchedule: '', + firstWorkflowTaskBackoff: '0s', + memo: null, + prevAutoResetPoints: null, +}); diff --git a/src/lib/utilities/decode-payload.test.ts b/src/lib/utilities/decode-payload.test.ts index 14e7ecca3..43212e951 100644 --- a/src/lib/utilities/decode-payload.test.ts +++ b/src/lib/utilities/decode-payload.test.ts @@ -1,36 +1,31 @@ -import { describe, expect, it, afterEach } from 'vitest'; +import { get } from 'svelte/store'; + +import { afterEach, describe, expect, it } from 'vitest'; +import { vi } from 'vitest'; import { + convertPayloadToJsonWithCodec, + decodeAllPotentialPayloadsWithCodec, decodePayload, decodePayloadAttributes, - convertPayloadToJsonWithWebsocket, - convertPayloadToJsonWithCodec, } from './decode-payload'; -import { getEventAttributes } from '../../lib/models/event-history'; -import { createWebsocket } from './data-converter-websocket'; import { - noRemoteDataConverterWorkflowStartedEvent, - dataConvertedFailureWorkflowStartedEvent, dataConvertedWorkflowStartedEvent, + getTestPayloadEvent, + getTestPayloadEventWithNullEncodedAttributes, + noRemoteDataConverterWorkflowStartedEvent, workflowStartedEvent, workflowStartedHistoryEvent, } from './decode-payload-test-fixtures'; -import WS from 'jest-websocket-mock'; -import { - dataConverterPort, - lastDataConverterStatus, - resetLastDataConverterSuccess, -} from '../stores/data-converter-config'; +import { parseWithBigInt, stringifyWithBigInt } from './parse-with-big-int'; +import { getEventAttributes } from '../../lib/models/event-history'; +import { resetLastDataConverterSuccess } from '../stores/data-converter-config'; import { codecEndpoint, lastDataEncoderStatus, resetLastDataEncoderSuccess, } from '../stores/data-encoder-config'; -import { get } from 'svelte/store'; -import { vi } from 'vitest'; -import { parseWithBigInt, stringifyWithBigInt } from './parse-with-big-int'; - const WebDecodePayload = { metadata: { encoding: 'YmluYXJ5L2VuY3J5cHRlZA==', @@ -63,6 +58,15 @@ const ProtobufEncoded = { data: 'InRlc3RAdGVzdC5jb20i', }; +const ProtobufEncodedWithMessageType = { + metadata: { + encoding: 'anNvbi9wcm90b2J1Zg==', + messageType: + 'dGVtcG9yYWwuYXBpLndvcmtmbG93LnYxLldvcmtmbG93RXhlY3V0aW9uU3RhcnRlZEV2ZW50QXR0cmlidXRlcw==', + }, + data: 'InRlc3RAdGVzdC5jb20i', +}; + const BinaryNullEncodedNoData = { metadata: { encoding: 'YmluYXJ5L251bGw=', @@ -80,104 +84,193 @@ const JsonObjectEncoded = { data: 'eyAiVHJhbnNmb3JtZXIiOiAiT3B0aW11c1ByaW1lIiB9', }; -describe('decodePayload', () => { +const JsonObjectEncodedWithConstructor = { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZA==', + }, + data: 'eyAiQ29uc3RydWN0b3JPdXRwdXQiOiAiT3B0aW11c1ByaW1lIiB9', +}; + +const JsonObjectDecoded = { Transformer: 'OptimusPrime' }; +const JsonObjectDecodedWithConstructor = { ConstructorOutput: 'OptimusPrime' }; + +describe('decodePayload with default returnDataOnly', () => { it('Should not decode a payload with encoding binary/encrypted', () => { expect(decodePayload(WebDecodePayload)).toEqual(WebDecodePayload); }); - - it('Should not decode a payload with encoding binary/encrypted', () => { - expect(decodePayload(BinaryNullEncodedNoData)).toEqual( - BinaryNullEncodedNoData, - ); + it('Should not decode a payload with encoding binary/null', () => { + expect(decodePayload(BinaryNullEncodedNoData)).toEqual(null); }); - it('Should decode a payload with encoding json/plain', () => { expect(decodePayload(JsonPlainEncoded)).toEqual(Base64Decoded); }); - it('Should decode a payload with encoding json/foo', () => { expect(decodePayload(JsonFooEncoded)).toEqual(Base64Decoded); }); - it('Should decode a payload with encoding json/protobuf', () => { expect(decodePayload(ProtobufEncoded)).toEqual(Base64Decoded); }); + it('Should decode a json payload with encoding json/plain', () => { + expect(decodePayload(JsonObjectEncoded)).toEqual(JsonObjectDecoded); + }); + it('Should decode a json payload with constructor keyword with encoding json/plain', () => { + expect(decodePayload(JsonObjectEncodedWithConstructor)).toEqual( + JsonObjectDecodedWithConstructor, + ); + }); }); -describe('convertPayloadToJsonWithWebsocket', () => { - afterEach(() => { - resetLastDataConverterSuccess(); +describe('decodePayload with returnDataOnly = false', () => { + it('Should not decode a payload with encoding binary/encrypted', () => { + expect(decodePayload(WebDecodePayload, false)).toEqual(WebDecodePayload); }); - it('Should convert a payload through data-converter and set the success status when the websocket is set and the websocket connects', async () => { - const ws = new WS('ws://localhost:1337'); - - // We need to respond to the websocket messages with the requestID so the - // websocket as promised library can resolve the promises correctly without - // the requestId it won't properly resolve - ws.nextMessage.then((data) => { - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const dataz = parseWithBigInt(data as any); - - ws.send( - stringifyWithBigInt({ - requestId: dataz.requestId, - content: { Transformer: 'OptimusPrime' }, - }), - ); - } catch (e) { - // ignore errors, test should fail if we don't respond - } - }); - - const websocket = createWebsocket('1337'); - await ws.connected; - - const convertedPayload = await convertPayloadToJsonWithWebsocket( - parseWithBigInt(stringifyWithBigInt(workflowStartedEvent)), - websocket, + it('Should not decode a payload with encoding binary/null', () => { + const fullDecodedPayload = { + metadata: { + encoding: 'binary/null', + }, + data: null, + }; + expect(decodePayload(BinaryNullEncodedNoData, false)).toEqual( + fullDecodedPayload, ); - const decodedPayload = decodePayloadAttributes(convertedPayload); - convertPayloadToJsonWithWebsocket( - parseWithBigInt(stringifyWithBigInt(workflowStartedEvent)), - {}, + }); + it('Should decode a payload with encoding json/plain', () => { + const fullDecodedPayload = { + metadata: { + encoding: 'json/plain', + type: 'Keyword', + }, + data: Base64Decoded, + }; + expect(decodePayload(JsonPlainEncoded, false)).toEqual(fullDecodedPayload); + }); + it('Should decode a payload with encoding json/foo', () => { + const fullDecodedPayload = { + metadata: { + encoding: 'json/foo', + type: 'Keyword', + }, + data: Base64Decoded, + }; + expect(decodePayload(JsonFooEncoded, false)).toEqual(fullDecodedPayload); + }); + it('Should decode a payload with encoding json/protobuf', () => { + const fullDecodedPayload = { + metadata: { + encoding: 'json/protobuf', + type: 'Keyword', + }, + data: Base64Decoded, + }; + expect(decodePayload(ProtobufEncoded, false)).toEqual(fullDecodedPayload); + }); + it('Should decode a payload with encoding json/protobuf with messageType', () => { + const fullDecodedPayload = { + metadata: { + encoding: 'json/protobuf', + messageType: + 'temporal.api.workflow.v1.WorkflowExecutionStartedEventAttributes', + }, + data: Base64Decoded, + }; + expect(decodePayload(ProtobufEncodedWithMessageType, false)).toEqual( + fullDecodedPayload, ); - expect(decodedPayload).toEqual(dataConvertedWorkflowStartedEvent); - - const dataConverterStatus = get(lastDataConverterStatus); - expect(dataConverterStatus).toEqual('success'); - - ws.close(); - WS.clean(); }); - - it('Should fail converting a payload through data-converter and set the status to error when the websocket is set and the websocket fails connection', async () => { - const websocket = createWebsocket('break', { - timeout: 1, - }); - - const convertedPayload = await convertPayloadToJsonWithWebsocket( - parseWithBigInt(stringifyWithBigInt(workflowStartedEvent)), - websocket, + it('Should decode a json payload with encoding json/plain', () => { + const fullDecodedPayload = { + metadata: { + encoding: 'json/plain', + type: 'Keyword', + }, + data: JsonObjectDecoded, + }; + expect(decodePayload(JsonObjectEncoded, false)).toEqual(fullDecodedPayload); + }); + it('Should decode a json payload with constructor keyword with encoding json/plain', () => { + const fullDecodedPayload = { + metadata: { + encoding: 'json/plain', + type: 'Keyword', + }, + data: JsonObjectDecodedWithConstructor, + }; + expect(decodePayload(JsonObjectEncodedWithConstructor, false)).toEqual( + fullDecodedPayload, ); - const decodedPayload = decodePayloadAttributes(convertedPayload); + }); +}); - expect(decodedPayload).toEqual(dataConvertedFailureWorkflowStartedEvent); +describe('decodePayloadAttributes', () => { + it('Should decodePayloadAttributes searchAttributes with indexedFields', () => { + const payload = { + searchAttributes: { + indexedFields: { + CandidateEmail: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZA==', + }, + data: 'IndoYWR1cEBsb2xjYXRzLmNvbSI=', + }, + }, + }, + }; + const result = { + searchAttributes: { + indexedFields: { CandidateEmail: 'whadup@lolcats.com' }, + }, + }; - const dataConverterStatus = get(lastDataConverterStatus); - expect(dataConverterStatus).toEqual('error'); + const decodedPayload = decodePayloadAttributes(payload); + expect(decodedPayload).toEqual(result); }); - it('Should skip converting a payload and set the status to notRequested when the websocket and port is not set', async () => { - const convertedPayload = await convertPayloadToJsonWithWebsocket( - parseWithBigInt(stringifyWithBigInt(workflowStartedEvent)), - ); - const decodedPayload = decodePayloadAttributes(convertedPayload); + it('Should decodePayloadAttributes searchAttributes without indexedFields', () => { + const payload = { + searchAttributes: { + CustomKeywordField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZA==', + }, + data: 'InRlc3RAdGVzdC5jb20i', + }, + }, + }; + const result = { + searchAttributes: { CustomKeywordField: 'test@test.com' }, + }; - expect(decodedPayload).toEqual(noRemoteDataConverterWorkflowStartedEvent); + const decodedPayload = decodePayloadAttributes(payload); + expect(decodedPayload).toEqual(result); + }); +}); - const dataConverterStatus = get(lastDataConverterStatus); - expect(dataConverterStatus).toEqual('notRequested'); +describe('decode all potential payloads', () => { + it('Should decode a payload with codec endpoint with encoding json/plain`', async () => { + const event = await decodeAllPotentialPayloadsWithCodec( + getTestPayloadEvent(), + 'default', + {}, + '', + ); + expect(event.input).toEqual({ payloads: ['test@test.com'] }); + expect(event.encodedAttributes).toEqual('a test attribute'); + expect(event.details.detail1).toEqual({ payloads: [{ test: 'detail' }] }); + }); + it('Should not decode a null payload with codec endpoint with encoding json/plain`', async () => { + const event = await decodeAllPotentialPayloadsWithCodec( + getTestPayloadEventWithNullEncodedAttributes(), + 'default', + {}, + '', + ); + expect(event.input).toEqual({ payloads: ['test@test.com'] }); + expect(event.encodedAttributes).toEqual(null); + expect(event.details.detail1).toEqual({ payloads: [{ test: 'detail' }] }); }); }); @@ -189,7 +282,7 @@ describe('convertPayloadToJsonWithCodec', () => { it('Should convert a payload through data-converter and set the success status when the endpoint is set and the endpoint connects', async () => { vi.stubGlobal('fetch', async () => { return { - json: () => Promise.resolve({ payloads: [JsonObjectEncoded] }), + json: () => Promise.resolve({ payloads: [JsonPlainEncoded] }), }; }); @@ -249,51 +342,74 @@ describe('convertPayloadToJsonWithCodec', () => { const dataConverterStatus = get(lastDataEncoderStatus); expect(dataConverterStatus).toEqual('notRequested'); }); -}); + it('Should not include credentials with the request for cookie based authentication of data converters by default', async () => { + const mockFetch = vi.fn(async () => { + return { + json: () => Promise.resolve({ payloads: [JsonPlainEncoded] }), + }; + }); -// Integration test -describe('getEventAttributes', () => { - afterEach(() => { - resetLastDataEncoderSuccess(); - resetLastDataConverterSuccess(); + vi.stubGlobal('fetch', mockFetch); + + const endpoint = 'http://localhost:1337'; + await convertPayloadToJsonWithCodec({ + attributes: parseWithBigInt(stringifyWithBigInt(workflowStartedEvent)), + namespace: 'default', + settings: { + codec: { + endpoint, + }, + }, + }); + + expect(mockFetch).toBeCalledWith( + expect.any(String), + expect.not.objectContaining({ credentials: 'same-origin' }), + ); }); - it('Should convert a payload through data-converter and set the success status when the endpoint is set locally and the endpoint connects', async () => { - vi.stubGlobal('fetch', async () => { + it('Should include cross-origin credentials with the request for cookie based authentication of data converters when includeCredentials is set', async () => { + const mockFetch = vi.fn(async () => { return { - json: () => Promise.resolve({ payloads: [JsonObjectEncoded] }), + json: () => Promise.resolve({ payloads: [JsonPlainEncoded] }), }; }); - const endpoint = 'http://localhost:1337'; - codecEndpoint.set(endpoint); + vi.stubGlobal('fetch', mockFetch); - const decodedPayload = await getEventAttributes({ - historyEvent: parseWithBigInt( - stringifyWithBigInt(workflowStartedHistoryEvent), - ), + const endpoint = 'http://localhost:1337'; + await convertPayloadToJsonWithCodec({ + attributes: parseWithBigInt(stringifyWithBigInt(workflowStartedEvent)), namespace: 'default', settings: { codec: { - endpoint: '', + endpoint, + includeCredentials: true, }, }, }); - expect(decodedPayload).toEqual(dataConvertedWorkflowStartedEvent); - const dataConverterStatus = get(lastDataEncoderStatus); - expect(dataConverterStatus).toEqual('success'); + expect(mockFetch).toBeCalledWith( + expect.any(String), + expect.objectContaining({ credentials: 'include' }), + ); }); - it('Should convert a payload through data-converter and set the success status when both the endpoint and websocket is set and the endpoint connects', async () => { - // tslint:disable-next-line +}); + +// Integration test +describe('getEventAttributes', () => { + afterEach(() => { + resetLastDataEncoderSuccess(); + resetLastDataConverterSuccess(); + }); + it('Should convert a payload through data-converter and set the success status when the endpoint is set locally and the endpoint connects', async () => { vi.stubGlobal('fetch', async () => { return { - json: () => Promise.resolve({ payloads: [JsonObjectEncoded] }), + json: () => Promise.resolve({ payloads: [JsonPlainEncoded] }), }; }); const endpoint = 'http://localhost:1337'; codecEndpoint.set(endpoint); - dataConverterPort.set('3889'); const decodedPayload = await getEventAttributes({ historyEvent: parseWithBigInt( diff --git a/src/lib/utilities/decode-payload.ts b/src/lib/utilities/decode-payload.ts index ca15fb9ae..53b92caca 100644 --- a/src/lib/utilities/decode-payload.ts +++ b/src/lib/utilities/decode-payload.ts @@ -1,131 +1,245 @@ -import type { Payload } from '$types'; +import { get } from 'svelte/store'; -import { dataConverterWebsocket } from '$lib/utilities/data-converter-websocket'; -import type { DataConverterWebsocketInterface } from '$lib/utilities/data-converter-websocket'; - -import { convertPayloadWithWebsocket } from '$lib/services/data-converter'; -import { convertPayloadsWithCodec } from '$lib/services/data-encoder'; +import { page } from '$app/stores'; +import { decodePayloadsWithCodec } from '$lib/services/data-encoder'; +import { authUser } from '$lib/stores/auth-user'; import type { codecEndpoint, + includeCredentials, passAccessToken, } from '$lib/stores/data-encoder-config'; +import type { DownloadEventHistorySetting } from '$lib/stores/events'; +import type { + Failure, + Memo, + Payloads, + Payload as RawPayload, +} from '$lib/types'; +import type { + EventAttribute, + EventRequestMetadata, + Payload, + WorkflowEvent, +} from '$lib/types/events'; +import type { Optional, Replace, Settings } from '$lib/types/global'; import { atob } from './atob'; +import { getCodecEndpoint } from './get-codec'; +import { has } from './has'; +import { isObject } from './is'; import { parseWithBigInt } from './parse-with-big-int'; +export type PotentiallyDecodable = + | Payloads + | Record; + export type Decode = { convertPayloadToJsonWithCodec: typeof convertPayloadToJsonWithCodec; - convertPayloadToJsonWithWebsocket: typeof convertPayloadToJsonWithWebsocket; decodePayloadAttributes: typeof decodePayloadAttributes; }; export type DecodeFunctions = { convertWithCodec?: Decode['convertPayloadToJsonWithCodec']; - convertWithWebsocket?: Decode['convertPayloadToJsonWithWebsocket']; decodeAttributes?: Decode['decodePayloadAttributes']; encoderEndpoint?: typeof codecEndpoint; codecPassAccessToken?: typeof passAccessToken; + codecIncludeCredentials?: typeof includeCredentials; +}; + +const toArray = (payloads: Payload | Payload[]): Payload[] => { + if (Array.isArray(payloads)) { + return payloads; + } else { + return [payloads]; + } +}; + +const decodeMetadata = (metadata: Record) => { + return Object.entries(metadata).reduce( + (acc, [key, value]) => { + acc[key] = atob(String(value)); + return acc; + }, + {} as Record, + ); }; export function decodePayload( payload: Payload, + returnDataOnly: boolean = true, // This could decode to any object. So we either use the payload object passed in or decode it - // eslint-disable-next-line @typescript-eslint/no-explicit-any -): Payload | Record | string { +): unknown | Payload | null { + if (payload === null) { + return payload; + } + + try { + const data = parseWithBigInt(atob(String(payload?.data ?? ''))); + if (returnDataOnly) return data; + const metadata = decodeMetadata(payload?.metadata); + return { + metadata, + data, + }; + } catch (_e) { + console.warn('Could not parse payload: ', _e); + // Couldn't correctly decode this just let the user deal with the data as is + } + const encoding = atob(String(payload?.metadata?.encoding ?? '')); - // Help users out with an english encoding - (payload.metadata.encodingDecoded as unknown as string) = encoding; - - if (encoding.startsWith('json/')) { - try { - return parseWithBigInt(atob(String(payload.data))); - } catch (_e) { - // Couldn't correctly decode this just let the user deal with the data as is - } + if (encoding === 'binary/null') { + if (returnDataOnly) return null; + const metadata = decodeMetadata(payload?.metadata); + return { + metadata, + data: null, + }; } return payload; } -export const decodePayloadAttributes = ( - eventAttribute: EventAttribute, -): EventAttribute => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const anyAttributes = eventAttribute as any; - +export const decodePayloadAttributes = < + T extends Optional, +>( + eventAttribute: T, + returnDataOnly: boolean = true, +): Replace< + T, + Optional +> => { // Decode Search Attributes - if (anyAttributes?.searchAttributes?.indexedFields) { - const searchAttributes = anyAttributes?.searchAttributes?.indexedFields; + if ( + has(eventAttribute, 'searchAttributes') && + has(eventAttribute.searchAttributes, 'indexedFields') + ) { + const searchAttributes = eventAttribute.searchAttributes.indexedFields; + Object.entries(searchAttributes).forEach(([key, value]) => { + searchAttributes[key] = decodePayload(value, returnDataOnly); + }); + } else if (has(eventAttribute, 'searchAttributes')) { + // Decode Search Attributes on UpsertWorkflowSearchAttributes + const searchAttributes = eventAttribute.searchAttributes; Object.entries(searchAttributes).forEach(([key, value]) => { - searchAttributes[key] = decodePayload(value); + searchAttributes[key] = decodePayload(value, returnDataOnly); }); } // Decode Memo - if (anyAttributes?.memo?.fields) { - const memo = anyAttributes?.memo?.fields; + if (has(eventAttribute, 'memo') && has(eventAttribute.memo, 'fields')) { + const memo = eventAttribute.memo.fields; Object.entries(memo).forEach(([key, value]) => { - memo[key] = decodePayload(value); + memo[key] = decodePayload(value, returnDataOnly); }); } // Decode Header - if (anyAttributes?.header?.fields) { - const header = anyAttributes?.header?.fields; + if (has(eventAttribute, 'header') && has(eventAttribute.header, 'fields')) { + const header = eventAttribute.header.fields; Object.entries(header).forEach(([key, value]) => { - header[key] = decodePayload(value); + header[key] = decodePayload(value, returnDataOnly); }); } // Decode Query Result // This one is a best guess from the previous codebase and needs verified - if (anyAttributes?.queryResult) { - const queryResult = anyAttributes?.queryResult; + if (has(eventAttribute, 'queryResult')) { + const queryResult = eventAttribute?.queryResult; Object.entries(queryResult).forEach(([key, value]) => { - queryResult[key] = decodePayload(value); + queryResult[key] = decodePayload(value, returnDataOnly); }); } - return anyAttributes; + return eventAttribute; +}; + +const decodeReadablePayloads = + (settings: Settings) => + async ( + payloads: unknown[], + returnDataOnly: boolean = true, + ): Promise => { + if (getCodecEndpoint(settings)) { + // Convert Payload data + const awaitData = await decodePayloadsWithCodec({ + payloads: { payloads }, + settings, + }); + return (awaitData?.payloads ?? []).map((p) => + decodePayload(p, returnDataOnly), + ); + } else { + return payloads.map((p) => decodePayload(p, returnDataOnly)); + } + }; + +export const decodePayloads = + (settings: Settings) => + async (payloads: unknown[]): Promise => { + if (getCodecEndpoint(settings)) { + // Convert Payload data + const awaitData = await decodePayloadsWithCodec({ + payloads: { payloads }, + settings, + }); + return awaitData?.payloads ?? []; + } else { + return payloads; + } + }; + +const keyIs = (key: string, ...validKeys: string[]) => { + for (const validKey of validKeys) { + if (key === validKey) return true; + } + return false; +}; + +export const decodeSingleReadablePayloadWithCodec = async ( + payload: RawPayload | Payload, + settings: Settings = get(page).data.settings, +): Promise => { + try { + const decode = decodeReadablePayloads(settings); + const data = await decode([payload]); + const result = data[0]; + return result || ''; + } catch { + return ''; + } }; export const decodeAllPotentialPayloadsWithCodec = async ( - anyAttributes: any, - namespace: string, - settings: Settings, - accessToken: string, -): Promise => { + anyAttributes: EventAttribute | PotentiallyDecodable | Failure, + namespace: string = get(page).params.namespace, + settings: Settings = get(page).data.settings, + accessToken: string = get(authUser).accessToken, +): Promise => { + const decode = decodeReadablePayloads(settings); + if (anyAttributes) { for (const key of Object.keys(anyAttributes)) { - if (key === 'payloads') { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let JSONPayload: string | Payload | Record; - const payloads = anyAttributes[key]; - if (settings?.codec?.endpoint) { - // Convert Payload data - const awaitData = await convertPayloadsWithCodec({ - payloads: { payloads }, + if (keyIs(key, 'payloads', 'encodedAttributes') && anyAttributes[key]) { + const data = toArray(anyAttributes[key]); + const decoded = await decode(data); + anyAttributes[key] = keyIs(key, 'encodedAttributes') + ? decoded[0] + : decoded; + } else { + const next = anyAttributes[key]; + if (isObject(next)) { + anyAttributes[key] = await decodeAllPotentialPayloadsWithCodec( + next, namespace, settings, accessToken, - }); - JSONPayload = (awaitData?.payloads ?? []).map(decodePayload); - } else { - JSONPayload = payloads.map(decodePayload); + ); } - anyAttributes[key] = JSONPayload; - } else if (typeof anyAttributes[key] === 'object') { - anyAttributes[key] = await decodeAllPotentialPayloadsWithCodec( - anyAttributes[key], - namespace, - settings, - accessToken, - ); } } } @@ -133,39 +247,66 @@ export const decodeAllPotentialPayloadsWithCodec = async ( return anyAttributes; }; -export const decodeAllPotentialPayloadsWithWebsockets = async ( - anyAttributes: any, - ws: DataConverterWebsocketInterface, -): Promise => { +export const isSinglePayload = (payload: unknown): boolean => { + if (!isObject(payload)) return false; + const keys = Object.keys(payload); + return ( + keys.length === 2 && keys.includes('metadata') && keys.includes('data') + ); +}; + +export const cloneAllPotentialPayloadsWithCodec = async ( + anyAttributes: + | PotentiallyDecodable + | EventAttribute + | WorkflowEvent + | Memo + | null, + namespace: string, + settings: Settings, + accessToken: string, + decodeSetting: DownloadEventHistorySetting = 'readable', + returnDataOnly: boolean = true, +): Promise< + PotentiallyDecodable | EventAttribute | WorkflowEvent | Memo | null +> => { + if (!anyAttributes) return anyAttributes; + + const decode = + decodeSetting === 'readable' + ? decodeReadablePayloads(settings) + : decodePayloads(settings); + const clone = { ...anyAttributes }; if (anyAttributes) { - for (const key of Object.keys(anyAttributes)) { - if (key === 'payloads') { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let JSONPayload: string | Payload | Record; - const payloads = anyAttributes[key]; - if (ws?.hasWebsocket) { - // Convert Payload data - const awaitData = await Promise.all( - (payloads ?? []).map( - async (payload) => - await convertPayloadWithWebsocket(payload, ws.websocket), - ), + // Now that we can have single Payload that is not an array (Nexus) + if (isSinglePayload(clone)) { + const data = toArray(clone as Payload); + const decoded = await decode(data, returnDataOnly); + return decoded?.[0] || clone; + } + + for (const key of Object.keys(clone)) { + if (keyIs(key, 'payloads', 'encodedAttributes') && clone[key]) { + const data = toArray(clone[key]); + const decoded = await decode(data, returnDataOnly); + clone[key] = keyIs(key, 'encodedAttributes') ? decoded[0] : decoded; + } else { + const next = clone[key]; + if (isObject(next)) { + clone[key] = await cloneAllPotentialPayloadsWithCodec( + next, + namespace, + settings, + accessToken, + decodeSetting, + returnDataOnly, ); - JSONPayload = awaitData; - } else { - JSONPayload = payloads.map(decodePayload); } - anyAttributes[key] = JSONPayload; - } else if (typeof anyAttributes[key] === 'object') { - anyAttributes[key] = await decodeAllPotentialPayloadsWithWebsockets( - anyAttributes[key], - ws, - ); } } } - return anyAttributes; + return clone; }; export const convertPayloadToJsonWithCodec = async ({ @@ -174,29 +315,15 @@ export const convertPayloadToJsonWithCodec = async ({ settings, accessToken, }: { - attributes: any; -} & EventRequestMetadata): Promise => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const anyAttributes = attributes as any; + attributes: EventAttribute | PotentiallyDecodable | Failure; +} & EventRequestMetadata): Promise< + EventAttribute | PotentiallyDecodable | Failure +> => { const decodedAttributes = await decodeAllPotentialPayloadsWithCodec( - anyAttributes, + attributes, namespace, settings, accessToken, ); return decodedAttributes; }; - -export const convertPayloadToJsonWithWebsocket = async ( - attributes: any, - websocket?: DataConverterWebsocketInterface, -): Promise => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const anyAttributes = attributes as any; - const ws = websocket ?? dataConverterWebsocket; - const decodedAttributes = await decodeAllPotentialPayloadsWithWebsockets( - anyAttributes, - ws, - ); - return decodedAttributes; -}; diff --git a/src/lib/utilities/encode-payload.test.ts b/src/lib/utilities/encode-payload.test.ts new file mode 100644 index 000000000..7fc3d99c3 --- /dev/null +++ b/src/lib/utilities/encode-payload.test.ts @@ -0,0 +1,64 @@ +import { describe, expect, it } from 'vitest'; + +import { + parseWithBigInt, + stringifyWithBigInt, +} from '$lib/utilities/parse-with-big-int'; + +import { encodePayloads, getSinglePayload } from './encode-payload'; + +describe('getSinglePayload', () => { + it('should return single payload from single payload', () => { + const payload = [{ foo: 'bar' }]; + const singlePayload = getSinglePayload(stringifyWithBigInt(payload)); + expect(parseWithBigInt(singlePayload)).toEqual(payload[0]); + }); + it('should return single payload from multiple payloads', () => { + const payload = ['input1', 'input2']; + const singlePayload = getSinglePayload(stringifyWithBigInt(payload)); + expect(parseWithBigInt(singlePayload)).toEqual(payload[0]); + }); + it('should return empty string from no payload', () => { + const singlePayload = getSinglePayload(''); + expect(singlePayload).toEqual(''); + }); + it('should return string from string payload', () => { + const payload = 'cats'; + const singlePayload = getSinglePayload(stringifyWithBigInt(payload)); + expect(parseWithBigInt(singlePayload)).toEqual(payload); + }); +}); + +describe('encodePayloads', () => { + it('should encode single simple string payload', async () => { + const payload = await encodePayloads({ + input: stringifyWithBigInt('cats'), + encoding: 'json/plain', + }); + const expectedEncodedPayload = [ + { + data: 'ImNhdHMi', + metadata: { + encoding: 'anNvbi9wbGFpbg==', + }, + }, + ]; + expect(payload).toEqual(expectedEncodedPayload); + }); + it('should encode bigInt string payload', async () => { + const input = { foo: 1234213412398883n }; + const payload = await encodePayloads({ + input: stringifyWithBigInt(input), + encoding: 'json/plain', + }); + const expectedEncodedPayload = [ + { + data: 'eyJmb28iOjEyMzQyMTM0MTIzOTg4ODN9', + metadata: { + encoding: 'anNvbi9wbGFpbg==', + }, + }, + ]; + expect(payload).toEqual(expectedEncodedPayload); + }); +}); diff --git a/src/lib/utilities/encode-payload.ts b/src/lib/utilities/encode-payload.ts new file mode 100644 index 000000000..12273ff6f --- /dev/null +++ b/src/lib/utilities/encode-payload.ts @@ -0,0 +1,75 @@ +import { get } from 'svelte/store'; + +import type { PayloadInputEncoding } from '$lib/components/payload-input-with-encoding.svelte'; +import { encodePayloadsWithCodec } from '$lib/services/data-encoder'; +import { dataEncoder } from '$lib/stores/data-encoder'; +import type { Payloads } from '$lib/types'; +import { btoa } from '$lib/utilities/btoa'; +import { + parseWithBigInt, + stringifyWithBigInt, +} from '$lib/utilities/parse-with-big-int'; + +export const getSinglePayload = (decodedValue: string): string => { + if (decodedValue) { + const parsedValue = parseWithBigInt(decodedValue); + const firstPayload = Array.isArray(parsedValue) + ? parsedValue?.[0] + : parsedValue; + if (firstPayload) { + return stringifyWithBigInt(firstPayload); + } + } + return ''; +}; + +export const setBase64Payload = ( + payload: unknown, + encoding: PayloadInputEncoding = 'json/plain', + messageType = '', +) => { + if (messageType) { + return { + metadata: { + encoding: btoa(encoding), + messageType: btoa(messageType), + }, + data: btoa(stringifyWithBigInt(payload)), + }; + } + return { + metadata: { + encoding: btoa(encoding), + }, + data: btoa(stringifyWithBigInt(payload)), + }; +}; + +type EncodePayloads = { + input: string; + encoding: PayloadInputEncoding; + messageType?: string; + encodeWithCodec?: boolean; +}; + +export const encodePayloads = async ({ + input, + encoding, + messageType = '', + encodeWithCodec = true, +}: EncodePayloads): Promise => { + let payloads = null; + + if (input) { + const parsedInput = parseWithBigInt(input); + payloads = [setBase64Payload(parsedInput, encoding, messageType)]; + const endpoint = get(dataEncoder).endpoint; + if (endpoint && encodeWithCodec) { + const awaitData = await encodePayloadsWithCodec({ + payloads: { payloads }, + }); + payloads = awaitData?.payloads ?? null; + } + } + return payloads; +}; diff --git a/src/lib/utilities/encode-uri.test.ts b/src/lib/utilities/encode-uri.test.ts index 5271c0d41..03bab4616 100644 --- a/src/lib/utilities/encode-uri.test.ts +++ b/src/lib/utilities/encode-uri.test.ts @@ -1,20 +1,51 @@ import { describe, expect, it } from 'vitest'; -import { encodeURIForSvelte, decodeURIForSvelte } from './encode-uri'; + +import { decodeURIForSvelte, encodeURIForSvelte } from './encode-uri'; describe('encodeURIForSvelte', () => { it('should encode reserved URI characters, \\ and %', () => { - const path = encodeURIForSvelte(',./;\'[]-=<>?:"{}|_+!@#$%^&*()`~)'); + const path = encodeURIForSvelte('Test\\_With@%Many!!,Char/acters'); + expect(path).toEqual('Test%5C_With%40%25Many!!%2CChar%2Facters'); + }); + + it('should encode reserved URI characters : # &', () => { + const path = encodeURIForSvelte('::Test::With##Many&&Characters::'); expect(path).toEqual( - '%2C.%2F;\'[]-%3D<>%3F%3A"{}|_%2B!%40%23%24%25^%26*()`~)', + '%3A%3ATest%3A%3AWith%23%23Many%26%26Characters%3A%3A', ); }); + + it('should encode spaces', () => { + const path = encodeURIForSvelte(' This is a test '); + expect(path).toEqual('%20This%20is%20a%20test%20'); + }); + + it('should encode already encoded characters', () => { + const path = encodeURIForSvelte('Workflow%25ID%24'); + expect(path).toEqual('Workflow%2525ID%2524'); + }); }); describe('decodeURIForSvelte', () => { it('should decode reserved URI characters, \\ and %', () => { + const path = decodeURIForSvelte('Test%5C_With%40%25Many!!%2CChar%2Facters'); + expect(path).toEqual('Test\\_With@%Many!!,Char/acters'); + }); + + it('should decode reserved URI characters : # &', () => { const path = decodeURIForSvelte( - '%2C.%2F;\'[]-%3D<>%3F%3A"{}|_%2B!%40%23%24%25^%26*()`~)', + '%3A%3ATest%3A%3AWith%23%23Many%26%26Characters%3A%3A', ); - expect(path).toEqual(',./;\'[]-=<>?:"{}|_+!@#$%^&*()`~)'); + expect(path).toEqual('::Test::With##Many&&Characters::'); + }); + + it('should decode spaces', () => { + const path = decodeURIForSvelte('%20This%20is%20a%20test%20'); + expect(path).toEqual(' This is a test '); + }); + + it('should decoded already encoded characters', () => { + const path = decodeURIForSvelte('Workflow%2525ID%2524'); + expect(path).toEqual('Workflow%25ID%24'); }); }); diff --git a/src/lib/utilities/encode-uri.ts b/src/lib/utilities/encode-uri.ts index 9814d06df..2c30db568 100644 --- a/src/lib/utilities/encode-uri.ts +++ b/src/lib/utilities/encode-uri.ts @@ -1,36 +1,9 @@ -// Encode reserved URI characters, \ and % -// TODO: related issue https://github.com/sveltejs/kit/issues/3069 export function encodeURIForSvelte(uri: string): string { - return uri - .replace(/%/g, '%25') - .replace(/,/g, '%2C') - .replace(/\//g, '%2F') - .replace(/\\/g, '%5C') - .replace(/\?/g, '%3F') - .replace(/:/g, '%3A') - .replace(/@/g, '%40') - .replace(/&/g, '%26') - .replace(/=/g, '%3D') - .replace(/\+/g, '%2B') - .replace(/\$/g, '%24') - .replace(/#/g, '%23'); + if (uri) return encodeURIComponent(uri); + return uri; } -// Decodes reserved URI characters, \ and % that are not automatically decoded by svelte kit/vite. -// Note: Using decodeURIComponent is not going to work after vite's decodeURI as it errors on strings like %myworkflowid -// TODO: related issue https://github.com/sveltejs/kit/issues/3069 export function decodeURIForSvelte(uri: string): string { - return uri - .replace(/%2C/g, ',') - .replace(/%2F/g, '/') - .replace(/%5C/g, '\\') - .replace(/%3F/g, '?') - .replace(/%3A/g, ':') - .replace(/%40/g, '@') - .replace(/%26/g, '&') - .replace(/%3D/g, '=') - .replace(/%2B/g, '+') - .replace(/%24/g, '$') - .replace(/%23/g, '#') - .replace(/%25/g, '%'); + if (uri) return decodeURIComponent(uri); + return uri; } diff --git a/src/lib/utilities/event-formatting.test.ts b/src/lib/utilities/event-formatting.test.ts new file mode 100644 index 000000000..370487d19 --- /dev/null +++ b/src/lib/utilities/event-formatting.test.ts @@ -0,0 +1,73 @@ +import { describe, expect, it } from 'vitest'; + +import { getDateFilterValue } from './event-formatting'; + +describe('getDateFilterValue', () => { + it('should return default values for feed view as undefined', () => { + const compact = false; + const sortOrder = 'descending'; + const showElapsed = 'false'; + const options = { compact, sortOrder, showElapsed }; + expect(getDateFilterValue(options)).toBe(undefined); + }); + + it('should return default values for compact view as undefined', () => { + const compact = true; + const sortOrder = 'ascending'; + const showElapsed = 'false'; + const options = { compact, sortOrder, showElapsed }; + expect(getDateFilterValue(options)).toBe(undefined); + }); + + it('should return string value for feed view when not sort order default', () => { + const compact = false; + const sortOrder = 'ascending'; + const showElapsed = 'false'; + const options = { compact, sortOrder, showElapsed }; + + const value = 'ascending:false'; + expect(getDateFilterValue(options)).toBe(value); + }); + + it('should return string value for feed view when show elapsed', () => { + const compact = false; + const sortOrder = 'descending'; + const showElapsed = 'true'; + const options = { compact, sortOrder, showElapsed }; + + const value = 'descending:true'; + expect(getDateFilterValue(options)).toBe(value); + }); + + it('should return string value for feed view when multiple non-default values', () => { + const compact = false; + const sortOrder = 'ascending'; + + const showElapsed = 'true'; + const options = { compact, sortOrder, showElapsed }; + + const value = 'ascending:true'; + expect(getDateFilterValue(options)).toBe(value); + }); + + it('should return string value for compact view when not sort order default', () => { + const compact = false; + const sortOrder = 'ascending'; + const showElapsed = 'false'; + const options = { compact, sortOrder, showElapsed }; + + const value = 'ascending:false'; + expect(getDateFilterValue(options)).toBe(value); + }); + + it('should return string value for compact view when multiple non-default values', () => { + const compact = true; + const sortOrder = 'ascending'; + + const showElapsed = 'true'; + const options = { compact, sortOrder, showElapsed }; + + const value = 'ascending:true'; + expect(getDateFilterValue(options)).toBe(value); + }); +}); diff --git a/src/lib/utilities/event-formatting.ts b/src/lib/utilities/event-formatting.ts new file mode 100644 index 000000000..fa912c86d --- /dev/null +++ b/src/lib/utilities/event-formatting.ts @@ -0,0 +1,24 @@ +import type { EventSortOrder } from '$lib/stores/event-view'; +import type { BooleanString } from '$lib/types/global'; + +type DateFilterOptions = { + compact: boolean; + sortOrder: EventSortOrder; + showElapsed: BooleanString; +}; + +export const getDateFilterValue = ({ + compact, + sortOrder, + showElapsed, +}: DateFilterOptions) => { + const isDefaultSortOrder = compact || sortOrder === 'descending'; + const isNotElapsedTime = showElapsed === 'false'; + const allDefaults = isDefaultSortOrder && isNotElapsedTime; + + if (allDefaults) { + return undefined; + } + + return `${sortOrder}:${showElapsed}`; +}; diff --git a/src/lib/utilities/event-link-href.test.ts b/src/lib/utilities/event-link-href.test.ts new file mode 100644 index 000000000..aac8499f6 --- /dev/null +++ b/src/lib/utilities/event-link-href.test.ts @@ -0,0 +1,96 @@ +import { describe, expect, it } from 'vitest'; + +import { getEventLinkHref } from './event-link-href'; + +describe('getEventLinkHref', () => { + it('should return event history event route when eventId exists', () => { + const link = { + workflowEvent: { + namespace: 'test-ns', + workflowId: 'test-wf', + runId: 'test-run', + eventRef: { + eventId: '42', + }, + }, + }; + + const result = getEventLinkHref(link); + expect(result).toBe( + '/namespaces/test-ns/workflows/test-wf/test-run/history/events/42', + ); + }); + + it('should return event history event route with eventId 1 for workflow execution started', () => { + const link = { + workflowEvent: { + namespace: 'test-ns', + workflowId: 'test-wf', + runId: 'test-run', + eventRef: { + eventType: 'EVENT_TYPE_WORKFLOW_EXECUTION_STARTED', + }, + }, + }; + + const result = getEventLinkHref(link); + expect(result).toBe( + '/namespaces/test-ns/workflows/test-wf/test-run/history/events/1', + ); + }); + + it('should return event history event route with requestId when requestId exists', () => { + const link = { + workflowEvent: { + namespace: 'test-ns', + workflowId: 'test-wf', + runId: 'test-run', + requestIdRef: { + requestId: 'req-123', + }, + }, + }; + + const result = getEventLinkHref(link); + expect(result).toBe( + '/namespaces/test-ns/workflows/test-wf/test-run/history/events/req-123', + ); + }); + + it('should return event history route as fallback', () => { + const link = { + workflowEvent: { + namespace: 'test-ns', + workflowId: 'test-wf', + runId: 'test-run', + }, + }; + + const result = getEventLinkHref(link); + expect(result).toBe( + '/namespaces/test-ns/workflows/test-wf/test-run/history', + ); + }); + + it('should prioritize eventId over other conditions', () => { + const link = { + workflowEvent: { + namespace: 'test-ns', + workflowId: 'test-wf', + runId: 'test-run', + eventRef: { + eventId: '99', + eventType: 'EVENT_TYPE_WORKFLOW_EXECUTION_STARTED', + }, + requestIdRef: { + requestId: 'req-456', + }, + }, + }; + + const result = getEventLinkHref(link); + expect(result).toBe( + '/namespaces/test-ns/workflows/test-wf/test-run/history/events/99', + ); + }); +}); diff --git a/src/lib/utilities/event-link-href.ts b/src/lib/utilities/event-link-href.ts new file mode 100644 index 000000000..d340f311b --- /dev/null +++ b/src/lib/utilities/event-link-href.ts @@ -0,0 +1,37 @@ +import type { EventLink } from '$lib/types/events'; + +import { routeForEventHistory, routeForEventHistoryEvent } from './route-for'; + +export const getEventLinkHref = (link: EventLink): string => { + if (link.workflowEvent?.eventRef?.eventId) { + return routeForEventHistoryEvent({ + namespace: link.workflowEvent.namespace, + workflow: link.workflowEvent.workflowId, + run: link.workflowEvent.runId, + eventId: link.workflowEvent.eventRef.eventId, + }); + } else if ( + link.workflowEvent?.eventRef?.eventType === + 'EVENT_TYPE_WORKFLOW_EXECUTION_STARTED' + ) { + return routeForEventHistoryEvent({ + namespace: link.workflowEvent.namespace, + workflow: link.workflowEvent.workflowId, + run: link.workflowEvent.runId, + eventId: '1', + }); + } else if (link.workflowEvent?.requestIdRef?.requestId) { + return routeForEventHistoryEvent({ + namespace: link.workflowEvent.namespace, + workflow: link.workflowEvent.workflowId, + run: link.workflowEvent.runId, + requestId: link.workflowEvent?.requestIdRef?.requestId, + }); + } else { + return routeForEventHistory({ + namespace: link.workflowEvent.namespace, + workflow: link.workflowEvent.workflowId, + run: link.workflowEvent.runId, + }); + } +}; diff --git a/src/lib/utilities/export-history.ts b/src/lib/utilities/export-history.ts index 1b1ca4a4e..c0d766089 100644 --- a/src/lib/utilities/export-history.ts +++ b/src/lib/utilities/export-history.ts @@ -1,29 +1,109 @@ +import { get } from 'svelte/store'; + +import { page } from '$app/stores'; + import { fetchRawEvents } from '$lib/services/events-service'; +import { authUser } from '$lib/stores/auth-user'; +import type { DownloadEventHistorySetting } from '$lib/stores/events'; +import type { HistoryEvent } from '$lib/types/events'; +import type { Settings } from '$lib/types/global'; + +import { + cloneAllPotentialPayloadsWithCodec, + decodePayloadAttributes, +} from './decode-payload'; +import { + getCodecEndpoint, + getCodecIncludeCredentials, + getCodecPassAccessToken, +} from './get-codec'; +import { stringifyWithBigInt } from './parse-with-big-int'; + +const decodePayloads = async ( + event: HistoryEvent, + settings: Settings, + decodeSetting: DownloadEventHistorySetting, +) => { + const endpoint = getCodecEndpoint(settings); + const passAccessToken = getCodecPassAccessToken(settings); + const includeCredentials = getCodecIncludeCredentials(settings); + const settingsWithLocalConfig = { + ...settings, + codec: { + ...settings?.codec, + endpoint, + passAccessToken, + includeCredentials, + }, + }; + + // Keep download in payload structure + const returnDataOnly = false; + try { + const convertedAttributes = await cloneAllPotentialPayloadsWithCodec( + event, + get(page).params.namespace, + settingsWithLocalConfig, + get(authUser).accessToken, + decodeSetting, + returnDataOnly, + ); + + return decodePayloadAttributes(convertedAttributes, returnDataOnly); + } catch (e) { + return event; + } +}; + +function download( + events: HistoryEvent[], + fileName: string, + contentType: string, +) { + const content = stringifyWithBigInt({ events }, null, 2); + const a = document.createElement('a'); + const file = new Blob([content], { type: contentType }); + a.href = URL.createObjectURL(file); + a.download = fileName; + a.click(); +} export const exportHistory = async ({ namespace, workflowId, runId, + settings, + decodeSetting, }: { namespace: string; workflowId: string; runId: string; + settings: Settings; + decodeSetting: DownloadEventHistorySetting; }) => { - const events = await fetchRawEvents({ - namespace, - workflowId, - runId, - sort: 'ascending', - }); - - const content = JSON.stringify({ events }, null, 2); - download(content, `${runId}/events.json`, 'text/plain'); - - function download(content: string, fileName: string, contentType: string) { - const a = document.createElement('a'); - const file = new Blob([content], { type: contentType }); - a.href = URL.createObjectURL(file); - a.download = fileName; - a.click(); + try { + const rawEvents = await fetchRawEvents({ + namespace, + workflowId, + runId, + sort: 'ascending', + }); + + if (decodeSetting === 'encoded') { + download(rawEvents, `${runId}/events.json`, 'text/plain'); + } else { + const decodedEvents = []; + for (const event of rawEvents) { + const decodedEvent = await decodePayloads( + event, + settings, + decodeSetting, + ); + decodedEvents.push(decodedEvent); + } + download(decodedEvents, `${runId}/events.json`, 'text/plain'); + } + } catch (e) { + console.error('Could not download event history'); } }; diff --git a/src/lib/utilities/export-workflows.ts b/src/lib/utilities/export-workflows.ts new file mode 100644 index 000000000..919e8a3a9 --- /dev/null +++ b/src/lib/utilities/export-workflows.ts @@ -0,0 +1,20 @@ +import type { WorkflowExecution } from '@temporalio/common'; + +import { stringifyWithBigInt } from './parse-with-big-int'; + +export const exportWorkflows = ( + workflows: WorkflowExecution[], + page: number, +) => { + const content = stringifyWithBigInt({ workflows }, null, 2); + const fileName = `workflows-${workflows.length}-${page}-${Date.now()}.json`; + download(content, fileName, 'text/plain'); + + function download(content: string, fileName: string, contentType: string) { + const a = document.createElement('a'); + const file = new Blob([content], { type: contentType }); + a.href = URL.createObjectURL(file); + a.download = fileName; + a.click(); + } +}; diff --git a/src/lib/utilities/focus-trap.test.ts b/src/lib/utilities/focus-trap.test.ts new file mode 100644 index 000000000..cc8380342 --- /dev/null +++ b/src/lib/utilities/focus-trap.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, it } from 'vitest'; + +import { getFocusableElements } from './focus-trap'; + +describe('getFocusableElements', () => { + it('should return focusable elements', () => { + const node = document.createElement('div'); + + node.appendChild(document.createElement('button')); + node.appendChild(document.createElement('input')); + node.appendChild(document.createElement('textarea')); + node.appendChild(document.createElement('select')); + node.appendChild(document.createElement('div')); + node.appendChild(document.createElement('a')); + + const focusable = getFocusableElements(node); + + expect(focusable.length).toBe(4); + }); + + it('should return elements with an href', () => { + const node = document.createElement('div'); + const link = document.createElement('a'); + link.setAttribute('href', '#'); + node.appendChild(link); + + const focusable = getFocusableElements(node); + + expect(focusable.length).toBe(1); + }); + + it('should return divs with contentEditable', () => { + const node = document.createElement('div'); + const editableDiv = document.createElement('div'); + editableDiv.setAttribute('contentEditable', 'true'); + node.appendChild(editableDiv); + + const focusable = getFocusableElements(node); + + expect(focusable.length).toBe(1); + }); + + it('should not return disabled elements', () => { + const node = document.createElement('div'); + + const button = document.createElement('button'); + button.setAttribute('disabled', 'true'); + node.appendChild(button); + node.appendChild(document.createElement('button')); + + const focusable = getFocusableElements(node); + + expect(focusable.length).toBe(1); + }); + + it('should not return elements with a tabindex of -1', () => { + const node = document.createElement('div'); + + const button = document.createElement('button'); + button.setAttribute('tabindex', '-1'); + node.appendChild(button); + node.appendChild(document.createElement('button')); + + const focusable = getFocusableElements(node); + + expect(focusable.length).toBe(1); + }); + + it('should return elements with a tabindex of 0', () => { + const node = document.createElement('ul'); + const listItem = document.createElement('li'); + listItem.setAttribute('tabindex', '0'); + node.appendChild(listItem); + node.appendChild(document.createElement('li')); + + const focusable = getFocusableElements(node); + + expect(focusable.length).toBe(1); + }); +}); diff --git a/src/lib/utilities/focus-trap.ts b/src/lib/utilities/focus-trap.ts new file mode 100644 index 000000000..d1dfd3d8e --- /dev/null +++ b/src/lib/utilities/focus-trap.ts @@ -0,0 +1,72 @@ +export const getFocusableElements = (node: HTMLElement) => + Array.from( + node.querySelectorAll( + '[href], button, textarea, input, div[contentEditable="true"], select, [tabindex][tabindex="0"]', + ), + ).filter( + (element) => + !element.hasAttribute('disabled') && + !(element.getAttribute('tabindex') === '-1'), + ); + +export const focusTrap = (node: HTMLElement, enabled: boolean) => { + let firstFocusable: HTMLElement; + let lastFocusable: HTMLElement; + + const onKeydown = (event: KeyboardEvent) => { + if (event.key === 'Tab') { + if (event.shiftKey) { + if (document.activeElement === firstFocusable) { + lastFocusable.focus(); + event.preventDefault(); + } + } else if (document.activeElement === lastFocusable) { + firstFocusable.focus(); + event.preventDefault(); + } + } + }; + + const setFocus = (fromObserver: boolean = false) => { + if (enabled === false) return; + + const focusable = getFocusableElements(node); + firstFocusable = focusable[0]; + lastFocusable = focusable[focusable.length - 1]; + + if (!fromObserver) firstFocusable?.focus(); + + firstFocusable?.addEventListener('keydown', onKeydown); + lastFocusable?.addEventListener('keydown', onKeydown); + }; + + const cleanUp = () => { + firstFocusable?.removeEventListener('keydown', onKeydown); + lastFocusable?.removeEventListener('keydown', onKeydown); + }; + + const onChange = ( + mutationRecords: MutationRecord[], + observer: MutationObserver, + ) => { + if (mutationRecords.length) { + cleanUp(); + setFocus(true); + } + return observer; + }; + const observer = new MutationObserver(onChange); + observer.observe(node, { childList: true, subtree: true }); + + setFocus(); + + return { + update(newArgs: boolean) { + enabled = newArgs; + newArgs ? setFocus() : cleanUp(); + }, + destroy() { + cleanUp(); + }, + }; +}; diff --git a/src/lib/utilities/form/superforms-example.svelte b/src/lib/utilities/form/superforms-example.svelte new file mode 100644 index 000000000..1dde61eaf --- /dev/null +++ b/src/lib/utilities/form/superforms-example.svelte @@ -0,0 +1,278 @@ + + +
+
+

Basic Form with Wrapper

+
+ + + + + +
+
+ +
+

Raw SuperForms Usage

+
+ + + + + + +
+ +
+

Form with Loading and Messages

+
+ + +
+ + + {#if $loadingErrors.description} + {$loadingErrors.description[0]} + {/if} +
+ + + + {#if $loadingMessage} +
+ {$loadingMessage} +
+ {/if} +
+
+ +
+

Form with Custom Validation

+
+ + + + + + + {#if $passwordAllErrors.length > 0} +
+

Please fix the following errors:

+
    + {#each $passwordAllErrors as error} +
  • {error.messages.join(', ')}
  • + {/each} +
+
+ {/if} +
+
+
diff --git a/src/lib/utilities/form/superforms-usage.md b/src/lib/utilities/form/superforms-usage.md new file mode 100644 index 000000000..0a8f0cf2b --- /dev/null +++ b/src/lib/utilities/form/superforms-usage.md @@ -0,0 +1,291 @@ +# SuperForms Usage Guide + +This guide demonstrates how to use SuperForms directly in this codebase, both with our minimal wrapper and with raw SuperForms. + +## Installation + +SuperForms is already installed in this project. The main dependencies are: + +- `sveltekit-superforms` - The core library +- `zod` - For schema validation + +## Option 1: Using the Minimal Wrapper + +We provide a simple `createSPAForm` wrapper that sets up SuperForms with SPA defaults. + +```typescript +import { createSPAForm } from '$lib/utilities/form/create-spa-form'; +import { z } from 'zod'; + +// Define your schema +const userSchema = z.object({ + username: z.string().min(3, 'Username must be at least 3 characters'), + email: z.string().email('Please enter a valid email'), +}); + +// Create the form +const { form, errors, constraints, enhance } = createSPAForm({ + schema: userSchema, + defaultValues: { + username: '', + email: '', + }, + options: { + onUpdate: async ({ form }) => { + // Handle form submission + console.log('Form submitted:', form); + // Make API call here + }, + onError: async ({ result }) => { + console.error('Form error:', result); + }, + resetForm: true, + }, +}); +``` + +```svelte + + + +
+ + + + + +
+``` + +## Option 2: Raw SuperForms Usage + +For more control, you can use SuperForms directly: + +```typescript +import { superForm } from 'sveltekit-superforms'; +import { zodClient } from 'sveltekit-superforms/adapters'; +import { z } from 'zod'; + +const schema = z.object({ + name: z.string().min(1, 'Name is required'), + age: z.number().min(18, 'Must be 18 or older'), +}); + +const { form, errors, constraints, enhance, submitting } = superForm( + {}, + { + SPA: true, + validators: zodClient(schema), + dataType: 'json', + resetForm: false, + + onUpdate: async ({ form }) => { + if (!form.valid) return; + + // Custom submission logic + const response = await fetch('/api/submit', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(form.data), + }); + + if (!response.ok) { + throw new Error('Submission failed'); + } + + // Handle success + console.log('Success!'); + }, + + onError: async ({ result }) => { + console.error('Form error:', result); + }, + }, +); +``` + +## Common Patterns + +### 1. Basic Form with Validation + +```svelte + + +
+ + {#if $errors.email} + {$errors.email[0]} + {/if} + + + {#if $errors.password} + {$errors.password[0]} + {/if} + + +
+``` + +### 2. Form with Loading State + +```svelte + + +
+ + +
+``` + +### 3. Form with Custom Error Handling + +```svelte + + +
+ + + + {#if $message} +
{$message}
+ {/if} +
+``` + +### 4. Complex Form with Nested Data + +```typescript +const complexSchema = z.object({ + user: z.object({ + name: z.string().min(1), + email: z.string().email(), + }), + preferences: z.object({ + theme: z.enum(['light', 'dark']), + notifications: z.boolean(), + }), + tags: z.array(z.string()), +}); + +const { form, errors, enhance } = createSPAForm({ + schema: complexSchema, + defaultValues: { + user: { name: '', email: '' }, + preferences: { theme: 'light', notifications: true }, + tags: [], + }, + options: { + onUpdate: async ({ form }) => { + // Handle complex form submission + }, + }, +}); +``` + +## Best Practices + +1. **Always use `use:enhance`** on your form elements +2. **Bind form values** with `bind:value={$form.fieldName}` +3. **Handle errors** by checking `$errors.fieldName` +4. **Use constraints** for HTML validation: `{...$constraints.fieldName}` +5. **Check submitting state** for loading indicators: `$submitting` +6. **Validate on client** but always validate on server for security + +## Resources + +- [SuperForms Documentation](https://superforms.rocks/) +- [Zod Documentation](https://zod.dev/) +- [SvelteKit Forms](https://kit.svelte.dev/docs/form-actions) diff --git a/src/lib/utilities/form/superforms.stories.svelte b/src/lib/utilities/form/superforms.stories.svelte new file mode 100644 index 000000000..1bcf940bc --- /dev/null +++ b/src/lib/utilities/form/superforms.stories.svelte @@ -0,0 +1,18 @@ + + + + + + + diff --git a/src/lib/utilities/format-bytes.ts b/src/lib/utilities/format-bytes.ts new file mode 100644 index 000000000..fad91cbc5 --- /dev/null +++ b/src/lib/utilities/format-bytes.ts @@ -0,0 +1,11 @@ +export const formatBytes = (bytes: number, decimals = 2) => { + if (!+bytes) return '0 Bytes'; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`; +}; diff --git a/src/lib/utilities/format-camel-case.test.ts b/src/lib/utilities/format-camel-case.test.ts index 3b61c26c6..ac5010fc9 100644 --- a/src/lib/utilities/format-camel-case.test.ts +++ b/src/lib/utilities/format-camel-case.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { format } from './format-camel-case'; describe('format', () => { diff --git a/src/lib/utilities/format-camel-case.ts b/src/lib/utilities/format-camel-case.ts index 63e2b00a1..2be0da0ab 100644 --- a/src/lib/utilities/format-camel-case.ts +++ b/src/lib/utilities/format-camel-case.ts @@ -3,26 +3,50 @@ const isUpperCase = (label: string, index: number): boolean => { return charCode >= 65 && charCode <= 90; }; -export const capitalize = (word: string): string => { +export const capitalize = (word?: string): string => { + if (!word) return ''; return word[0].toUpperCase() + word.slice(1); }; -const labelsToAddName = new Set(['workflowType']); +export const spaceBetweenCapitalLetters = (word: string): string => { + return capitalize(word) + .replace(/([A-Z])/g, ' $1') + .trim(); +}; + +const labelsToAddName: Readonly> = new Set(['workflowType']); +const labelsToChange: Readonly<{ [key: string]: string }> = { + workflowExecutionWorkflowId: 'workflowId', + workflowExecutionRunId: 'runId', + protocolInstanceId: 'updateId', + metaUpdateId: 'updateId', +}; + +const formatLabel = (label?: string): string => { + if (!label) return ''; -const addNameIfNeeded = (label?: string) => { + // Add name if needed if (labelsToAddName.has(label)) { return `${label}Name`; } + // Change label if needed + if (labelsToChange[label]) { + return labelsToChange[label]; + } + return label; }; export const format = (label?: string): string => { + if (!label) return ''; + if (label === 'id') return 'ID'; + let result = ''; let index = 0; - label = addNameIfNeeded(label); + label = formatLabel(label); - while (index < label?.length) { + while (index < label.length) { const current = label[index]; const next = label[index + 1]; diff --git a/src/lib/utilities/format-date.test.ts b/src/lib/utilities/format-date.test.ts new file mode 100644 index 000000000..9f95b4554 --- /dev/null +++ b/src/lib/utilities/format-date.test.ts @@ -0,0 +1,182 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { + formatDate, + formatUTCOffset, + getLocalTime, + getSelectedTimezone, + getUTCString, + isValidDate, +} from './format-date'; + +describe('formatDate', () => { + const date = '2022-04-13T16:29:35.630571Z'; + + it('should return an empty string if no date is provided', () => { + expect(formatDate(undefined)).toEqual(''); + expect(formatDate(null)).toEqual(''); + }); + + it('should error if there is an invalid date', () => { + const spy = vi.spyOn(console, 'error'); + formatDate('2022-04-13T16'); + expect(spy).toHaveBeenCalled(); + + formatDate('5 minutes'); + expect(spy).toHaveBeenCalled(); + }); + + it('should error if there is an invalid timezone', () => { + const spy = vi.spyOn(console, 'error'); + formatDate(date, 'bogus'); + expect(spy).toHaveBeenCalled(); + }); + + it('should default to UTC', () => { + expect(formatDate(date)).toEqual('2022-04-13 UTC 16:29:35.63'); + }); + + it('should format other timezones', () => { + expect(formatDate(date, 'Greenwich Mean Time')).toEqual( + '2022-04-13 GMT 16:29:35.63', + ); + expect(formatDate(date, 'Central Standard Time')).toEqual( + '2022-04-13 CDT 11:29:35.63', + ); + expect(formatDate(date, 'Pacific Daylight Time')).toEqual( + '2022-04-13 PDT 09:29:35.63', + ); + }); + + it('should format already formatted strings', () => { + expect(formatDate('2022-04-13 UTC 16:29:35.63')).toEqual( + '2022-04-13 UTC 16:29:35.63', + ); + }); + + it('should format local time', () => { + expect(formatDate(date, 'local')).toEqual('2022-04-13 UTC 16:29:35.63'); + }); + + it('should format relative local time', () => { + expect(formatDate(date, 'local', { relative: true })).toContain('ago'); + }); + + it('should not format other timezones as relative', () => { + expect(formatDate(date, 'UTC', { relative: true })).toEqual( + '2022-04-13 UTC 16:29:35.63', + ); + }); + + it('should format relative local time with a custom label', () => { + expect( + formatDate(date, 'local', { relative: true, relativeLabel: 'custom' }), + ).toContain('custom'); + }); + + it('should shorten format for local and other timezones', () => { + expect(formatDate(date, 'local', { abbrFormat: true })).toEqual( + '2022-04-13 16:29:35 PM', + ); + expect(formatDate(date, 'utc', { abbrFormat: true })).toEqual( + '2022-04-13 16:29:35 PM', + ); + }); + + it('should shorten format without seconds if there are none for local and other timezones', () => { + const dateWithoutSeconds = '2022-04-13T16:29:00.630571Z'; + expect( + formatDate(dateWithoutSeconds, 'local', { abbrFormat: true }), + ).toEqual('2022-04-13 16:29 PM'); + expect(formatDate(dateWithoutSeconds, 'utc', { abbrFormat: true })).toEqual( + '2022-04-13 16:29 PM', + ); + }); +}); + +describe('isValidDate', () => { + it('should return true if the value is a valid Date', () => { + expect(isValidDate('2022-04-13T11:29:32.633009Z')).toBe(true); + }); + + it('should return false if the value is not a valid Date', () => { + expect(isValidDate('bogus')).toBe(false); + expect(isValidDate('')).toBe(false); + }); +}); + +describe('formatUTCOffset', () => { + it('should return an empty string if the offset is undefined', () => { + expect(formatUTCOffset(undefined, 'UTC')).toBe(''); + }); + + it('should return the correct string if the offset is 0', () => { + expect(formatUTCOffset(0, 'UTC')).toBe('UTC±00:00'); + }); + + it('should return an empty string if the offset is positive', () => { + expect(formatUTCOffset(3, 'UTC')).toBe('UTC+03:00'); + }); + + it('should return an empty string if the offset is positive and double digits', () => { + expect(formatUTCOffset(10, 'UTC')).toBe('UTC+10:00'); + }); + + it('should return an empty string if the offset is negative', () => { + expect(formatUTCOffset(-3, 'UTC')).toBe('UTC-03:00'); + }); + + it('should return an empty string if the offset is negative and double digits', () => { + expect(formatUTCOffset(-10, 'UTC')).toBe('UTC-10:00'); + }); +}); + +describe('getLocalTime', () => { + it('should get the local timezone', () => { + expect(getLocalTime()).toBe('UTC'); + }); +}); + +describe('getSelectedTimezone', () => { + it('should get the abbreviation for the timezone', () => { + expect(getSelectedTimezone('Mountain Standard Time')).toBe( + 'Mountain Standard Time (MST)', + ); + }); + + it('should get the local timezone', () => { + expect(getSelectedTimezone('local')).toBe('UTC'); + }); + + it('should return the time format if there is no matching timezone found', () => { + expect(getSelectedTimezone('UTC')).toBe('UTC'); + }); +}); + +describe('getUTCString', () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2020-01-01').getTime()); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('should default to 00:00 for the current date', () => { + expect(getUTCString()).toBe('2020-01-01T00:00:00.000Z'); + }); + + it('should return a string with hours, minutes, and seconds set', () => { + expect(getUTCString({ hour: 1, minute: 1, second: 1 })).toBe( + '2020-01-01T01:01:01.000Z', + ); + expect(getUTCString({ hour: 1, minute: 61, second: 1 })).toBe( + '2020-01-01T02:01:01.000Z', + ); + expect(getUTCString({ hour: 1, minute: 1, second: 61 })).toBe( + '2020-01-01T01:02:01.000Z', + ); + expect(getUTCString({ hour: 25 })).toBe('2020-01-02T01:00:00.000Z'); + }); +}); diff --git a/src/lib/utilities/format-date.ts b/src/lib/utilities/format-date.ts index de6a53e86..2a241db31 100644 --- a/src/lib/utilities/format-date.ts +++ b/src/lib/utilities/format-date.ts @@ -1,30 +1,128 @@ -import { formatDistanceToNow, parseJSON } from 'date-fns'; +import { + formatDistanceToNow, + formatDistanceToNowStrict, + parseISO, + parseJSON, +} from 'date-fns'; import * as dateTz from 'date-fns-tz'; // `build` script fails on importing some of named CommonJS modules +import { + getTimezone, + type TimeFormat, + TimezoneOptions, + Timezones, +} from '$lib/stores/time-format'; + import { isTimestamp, timestampToDate, type ValidTime } from './format-time'; const pattern = 'yyyy-MM-dd z HH:mm:ss.SS'; export function formatDate( date: ValidTime | undefined | null, - timeFormat: TimeFormat | string = 'UTC', - relativeLabel = 'ago', + timeFormat: TimeFormat = 'UTC', + options: { + relative?: boolean; + relativeLabel?: string; + relativeStrict?: boolean; + abbrFormat?: boolean; + } = {}, ): string { if (!date) return ''; + const { + relative = false, + relativeLabel = 'ago', + relativeStrict = false, + abbrFormat = false, + } = options; + try { if (isTimestamp(date)) { date = timestampToDate(date); } - const parsed = parseJSON(date); + const parsed = parseJSON(new Date(date)); - if (timeFormat === 'local') return dateTz.format(parsed, pattern); - if (timeFormat === 'relative') - return formatDistanceToNow(parsed) + ` ${relativeLabel}`; + const format = abbrFormat + ? parsed.getSeconds() + ? 'yyyy-MM-dd HH:mm:ss a' + : 'yyyy-MM-dd HH:mm a' + : pattern; - return dateTz.formatInTimeZone(parsed, 'UTC', pattern); - } catch { + if (timeFormat === 'local') { + if (relative) + return relativeStrict + ? formatDistanceToNowStrict(parsed) + ` ${relativeLabel}` + : formatDistanceToNow(parsed) + ` ${relativeLabel}`; + return dateTz.format(parsed, format); + } + const timezone = getTimezone(timeFormat); + return dateTz.formatInTimeZone(parsed, timezone, format); + } catch (e) { + console.error('Error formatting date:', e); return ''; } } + +export function isValidDate(date: string): boolean { + const d = parseISO(date); + return d instanceof Date && !isNaN(d.getTime()); +} + +export function formatUTCOffset( + offset: number | undefined, + utc: string, +): string { + if (offset === undefined) return ''; + if (offset === 0) return `${utc}±00:00`; + const absoluteValue = Math.abs(offset); + const formattedOffset = + absoluteValue > 9 ? `${absoluteValue}:00` : `0${absoluteValue}:00`; + if (offset > 0) return `${utc}+${formattedOffset}`; + if (offset < 0) return `${utc}-${formattedOffset}`; +} + +export function getLocalTimezone(): string { + return Intl.DateTimeFormat().resolvedOptions().timeZone; +} + +export function getLocalTime(): string { + const localTimezone = getLocalTimezone(); + const localOption = TimezoneOptions.find(({ zones }) => + zones?.includes(localTimezone), + ); + return localOption + ? `${localOption.label} (${localOption.abbr})` + : localTimezone; +} + +export function getSelectedTimezone(timeFormat: TimeFormat): string { + if (timeFormat === 'local') return getLocalTime(); + + const selectedTimezone = Timezones[timeFormat]; + if (selectedTimezone) return `${timeFormat} (${selectedTimezone.abbr})`; + + return timeFormat; +} + +export function getUTCString({ + date = new Date(), + hour = 0, + minute = 0, + second = 0, +}: { + date?: Date; + hour?: string | number; + minute?: string | number; + second?: string | number; +} = {}): string { + const utcTime = Date.UTC( + date.getFullYear(), + date.getMonth(), + date.getDate(), + Number(hour), + Number(minute), + Number(second), + ); + return new Date(utcTime).toISOString(); +} diff --git a/src/lib/utilities/format-event-attributes.test.ts b/src/lib/utilities/format-event-attributes.test.ts index b5f5f75c3..92af28131 100644 --- a/src/lib/utilities/format-event-attributes.test.ts +++ b/src/lib/utilities/format-event-attributes.test.ts @@ -1,14 +1,18 @@ import { describe, expect, it } from 'vitest'; + +import { translate } from '$lib/i18n/translate'; + import { - formatAttributes, attributeGroups, formatAttemptsLeft, + formatAttributes, formatMaximumAttempts, formatRetryExpiration, - UnlimitedAttempts, - NoExpiration, } from './format-event-attributes'; +const UnlimitedAttempts = translate('workflows.unlimited'); +const NoExpiration = translate('workflows.no-expiration'); + const workflowEvent = { eventId: '1', eventTime: '2022-03-14T17:44:14.996241458Z', @@ -120,11 +124,6 @@ describe('formatAttributes', () => { expect(formattedAttributes.firstWorkflowTaskBackoff).toBe(undefined); }); - it('should remove values that are should be omitted', () => { - const formattedAttributes = formatAttributes(workflowEvent); - expect(formattedAttributes.header).toBe(undefined); - }); - it('should format nested attributes', () => { const formattedAttributes = formatAttributes(workflowEvent); expect(formattedAttributes.taskQueueName).toBe('rainbow-statuses'); @@ -134,18 +133,26 @@ describe('formatAttributes', () => { expect(formatAttemptsLeft(10, 3)).toBe(7); }); - it('should format attempts left with unlimited max attempts', () => { + it('should format attempts left with 0 max attempts to be unlimited', () => { expect(formatAttemptsLeft(0, 3)).toBe(UnlimitedAttempts); }); + it('should format attempts left with null max attempts to be unlimited', () => { + expect(formatAttemptsLeft(null, 18)).toBe(UnlimitedAttempts); + }); + it('should format max attempts left with limited max attempts', () => { expect(formatMaximumAttempts(2393)).toBe(2393); }); - it('should format max attempts left with unlimited max attempts', () => { + it('should format max attempts left with 0 max attempts', () => { expect(formatMaximumAttempts(0)).toBe(UnlimitedAttempts); }); + it('should format max attempts left with null max attempts', () => { + expect(formatMaximumAttempts(null)).toBe(UnlimitedAttempts); + }); + it('should format expiration with unlimited max attempts', () => { expect(formatRetryExpiration(0, '2022-12-25')).toBe(NoExpiration); }); @@ -166,6 +173,7 @@ describe('attributeGroups', () => { 'identity', 'firstExecutionRunId', 'attempt', + 'header', 'parentInitiatedEventId', ]); expect(groups.parent).toEqual([]); diff --git a/src/lib/utilities/format-event-attributes.ts b/src/lib/utilities/format-event-attributes.ts index fa9f873ed..6e8597627 100644 --- a/src/lib/utilities/format-event-attributes.ts +++ b/src/lib/utilities/format-event-attributes.ts @@ -1,60 +1,77 @@ +import { get } from 'svelte/store'; + +import type { I18nKey } from '$lib/i18n'; +import { translate } from '$lib/i18n/translate'; +import type { EventGroup } from '$lib/models/event-groups/event-groups'; +import { relativeTime, timeFormat } from '$lib/stores/time-format'; +import type { + EventAttribute, + EventAttributeKey, + IterableEvent, + PendingActivity, +} from '$lib/types/events'; +import { capitalize } from '$lib/utilities/format-camel-case'; import { formatDate } from '$lib/utilities/format-date'; import { + pendingActivityKeys, shouldDisplayAttribute, + shouldDisplayGroupAttribute, shouldDisplayNestedAttribute, + shouldDisplayPendingAttribute, } from '$lib/utilities/get-single-attribute-for-event'; -import { capitalize } from '$lib/utilities/format-camel-case'; export type CombinedAttributes = EventAttribute & { eventTime?: string; workflowExecutionRunId?: string; workflowExecutionWorkflowId?: string; + firstExecutionRunId?: string; + continuedExecutionRunId?: string; + newExecutionRunId?: string; + namespace?: string; }; -const keysToOmit: Readonly> = new Set(['header']); - const keysToExpand: Readonly> = new Set([ 'taskQueue', 'retryPolicy', 'parentWorkflowExecution', 'workflowExecution', + 'meta', ]); const keysToFormat: Readonly> = new Set(['maximumAttempts']); -export const UnlimitedAttempts = 'Unlimited'; -export const NoExpiration = 'No Expiration'; - export const formatRetryExpiration = ( maxAttempts: number, expiration: string, ): number | string => { if (maxAttempts === 0) { - return NoExpiration; + return translate('workflows.no-expiration'); } return expiration; }; export const formatAttemptsLeft = ( - maxAttempts: number, + maxAttempts: number | null, attempt: number, ): number | string => { - if (maxAttempts === 0) { - return UnlimitedAttempts; + if (!maxAttempts) { + return translate('workflows.unlimited'); } return maxAttempts - attempt; }; -export const formatMaximumAttempts = (maxAttempts: number): number | string => { - if (maxAttempts === 0) { - return UnlimitedAttempts; +export const formatMaximumAttempts = ( + maxAttempts: number | null, +): number | string => { + if (!maxAttempts) { + return translate('workflows.unlimited'); } return maxAttempts; }; const formatValue = (key: string, value: unknown) => { - if (key === 'maximumAttempts' && value === 0) { - return UnlimitedAttempts; + if (key === 'maximumAttempts' && !value) { + return translate('workflows.unlimited'); } return value; }; @@ -81,17 +98,60 @@ const formatNestedAttributes = ( } }; -export const formatAttributes = ( - event: IterableEvent, - { compact } = { compact: false }, +export const formatAttributes = (event: IterableEvent): CombinedAttributes => { + const attributes: CombinedAttributes = {}; + + if (event.attributes) { + for (const [key, value] of Object.entries(event.attributes)) { + const shouldDisplay = shouldDisplayAttribute(key, value); + if (shouldDisplay) attributes[key] = value; + formatNestedAttributes(attributes, key); + } + } + return attributes; +}; + +export const formatGroupAttributes = ( + group: EventGroup, +): CombinedAttributes => { + const attributes: CombinedAttributes = {}; + + group.eventList.forEach((event) => { + for (const [key, value] of Object.entries(event.attributes)) { + const shouldDisplay = shouldDisplayAttribute(key, value); + if (shouldDisplay) attributes[key] = value; + formatNestedAttributes(attributes, key); + } + }); + + Object.keys(attributes).forEach((key) => { + if (!shouldDisplayGroupAttribute(key, attributes[key])) + delete attributes[key]; + }); + return attributes; +}; + +export const formatPendingAttributes = ( + pendingActivity: PendingActivity, ): CombinedAttributes => { const attributes: CombinedAttributes = {}; - if (compact) attributes.eventTime = formatDate(event.eventTime); + const sortedEntries = Object.entries(pendingActivity).sort( + ([key1], [key2]) => { + return ( + pendingActivityKeys.indexOf(key1) - pendingActivityKeys.indexOf(key2) + ); + }, + ); - for (const [key, value] of Object.entries(event.attributes)) { - const shouldDisplay = shouldDisplayAttribute(key, value); - if (!keysToOmit.has(key) && shouldDisplay) attributes[key] = value; + for (const [key, value] of sortedEntries) { + const shouldDisplay = shouldDisplayPendingAttribute(key); + const formattedValue = key.toLowerCase().includes('time') + ? formatDate(String(value), get(timeFormat), { + relative: get(relativeTime), + }) + : value; + if (shouldDisplay) attributes[key] = formattedValue; formatNestedAttributes(attributes, key); } @@ -120,21 +180,22 @@ const attributeGroupings: Readonly = [ ]; type GroupingOption = { - label: string; - color: Color; + label: I18nKey; }; export const attributeGroupingProperties: Readonly< Record > = { - activity: { label: 'Activity', color: 'gray' }, - parent: { label: 'Parent', color: 'gray' }, - retryPolicy: { label: 'Retry Policy', color: 'gray' }, - schedule: { label: 'Schedule', color: 'gray' }, - searchAttributes: { label: 'Search Attributes', color: 'gray' }, - summary: { label: 'Summary', color: 'gray' }, - taskQueue: { label: 'Task Queue', color: 'gray' }, - workflow: { label: 'Workflow', color: 'gray' }, + activity: { label: 'events.attribute-group.activity' }, + parent: { label: 'events.attribute-group.parent' }, + retryPolicy: { label: 'events.attribute-group.retry-policy' }, + schedule: { label: 'events.attribute-group.schedule' }, + searchAttributes: { + label: 'events.attribute-group.search-attributes', + }, + summary: { label: 'events.attribute-group.summary' }, + taskQueue: { label: 'events.attribute-group.task-queue' }, + workflow: { label: 'events.attribute-group.workflow' }, }; export type AttributeGrouping = Partial< @@ -190,7 +251,7 @@ export const attributeGroups = ( } }); - for (const key of Object.keys(attributes)) { + for (const key in attributes) { const attributeGroup = attributeGroupings.find((group) => key.includes(group), ); diff --git a/src/lib/utilities/format-json.ts b/src/lib/utilities/format-json.ts new file mode 100644 index 000000000..8f490cdee --- /dev/null +++ b/src/lib/utilities/format-json.ts @@ -0,0 +1,16 @@ +import { parseWithBigInt, stringifyWithBigInt } from './parse-with-big-int'; + +export function formatJSON(value: string, space: 0 | 2): string { + if (!value) return ''; + + let result: string = ''; + + try { + const jsonData = parseWithBigInt(value); + result = stringifyWithBigInt(jsonData, undefined, space); + } catch { + result = value; + } + + return result; +} diff --git a/src/lib/utilities/format-time.test.ts b/src/lib/utilities/format-time.test.ts index a5cd12a05..bd5ca9396 100644 --- a/src/lib/utilities/format-time.test.ts +++ b/src/lib/utilities/format-time.test.ts @@ -1,10 +1,13 @@ import { describe, expect, it } from 'vitest'; + import { - getDuration, formatDistance, formatDistanceAbbreviated, - fromSecondsToMinutesAndSeconds, + formatDurationAbbreviated, + formatSecondsAbbreviated, fromSecondsToDaysOrHours, + fromSecondsToMinutesAndSeconds, + getDuration, getTimestampDifference, } from './format-time'; @@ -131,6 +134,98 @@ describe('getDuration', () => { ); expect(abbvDistancer).toBe('2years 9months 11d 4h 44m 18s'); }); + it('should get minutes/seconds duration with milliseconds of a start and end date', () => { + const start = '2022-04-13T16:29:35.630571Z'; + const end = '2022-04-13T16:35:21.300609Z'; + const duration = getDuration({ start, end }); + const distance = formatDistance({ start, end, includeMilliseconds: true }); + const abbvDistancer = formatDistanceAbbreviated({ + start, + end, + includeMilliseconds: true, + }); + expect(duration).toStrictEqual({ + days: 0, + hours: 0, + minutes: 5, + months: 0, + seconds: 45, + years: 0, + }); + expect(distance).toBe('5 minutes, 45 seconds 670ms'); + expect(abbvDistancer).toBe('5m 45s 670ms'); + }); + it('should get only milliseconds of a start and end date less than a second apart', () => { + const start = '2022-04-13T16:29:35.630571Z'; + const end = '2022-04-13T16:29:35.893821Z'; + const duration = getDuration({ start, end }); + const distance = formatDistance({ start, end, includeMilliseconds: true }); + const abbvDistancer = formatDistanceAbbreviated({ + start, + end, + includeMilliseconds: true, + }); + expect(duration).toStrictEqual({ + days: 0, + hours: 0, + minutes: 0, + months: 0, + seconds: 0, + years: 0, + }); + expect(distance).toBe('263ms'); + expect(abbvDistancer).toBe('263ms'); + }); + it('should get only milliseconds of a start and end date less than a second apart with includeMillisecondsForUnderSecond', () => { + const start = '2022-04-13T16:29:35.630571Z'; + const end = '2022-04-13T16:29:35.893821Z'; + const duration = getDuration({ start, end }); + const distance = formatDistance({ + start, + end, + includeMillisecondsForUnderSecond: true, + }); + const abbvDistancer = formatDistanceAbbreviated({ + start, + end, + includeMillisecondsForUnderSecond: true, + }); + expect(duration).toStrictEqual({ + days: 0, + hours: 0, + minutes: 0, + months: 0, + seconds: 0, + years: 0, + }); + expect(distance).toBe('263ms'); + expect(abbvDistancer).toBe('263ms'); + }); + it('should not get milliseconds of a start and end date more than a second apart with includeMillisecondsForUnderSecond', () => { + const start = '2022-04-13T16:29:33.630571Z'; + const end = '2022-04-13T16:29:35.893821Z'; + const duration = getDuration({ start, end }); + const distance = formatDistance({ + start, + end, + includeMillisecondsForUnderSecond: true, + }); + const abbvDistancer = formatDistanceAbbreviated({ + start, + end, + includeMillisecondsForUnderSecond: true, + }); + expect(duration).toStrictEqual({ + days: 0, + hours: 0, + minutes: 0, + months: 0, + seconds: 2, + years: 0, + }); + expect(distance).toBe('2 seconds'); + expect(abbvDistancer).toBe('2s'); + }); }); describe('fromSecondsToMinutesAndSeconds', () => { @@ -204,4 +299,38 @@ describe('getTimestampDifference', () => { const end = '2022-04-13T16:29:35.633009Z'; expect(getTimestampDifference(start, end)).toBe(50316513); }); + + describe('formatDurationAbbreviated', () => { + it('should return duration abbreviated with full milliseconds if under one second', () => { + expect(formatDurationAbbreviated('0.86920383s')).toBe('869.20383ms'); + }); + it('should return duration abbreviated with rounded milliseconds if under one minute', () => { + expect(formatDurationAbbreviated('13.439023207s')).toBe('13s, 439ms'); + }); + it('should return duration abbreviated with no milliseconds if over one minute', () => { + expect(formatDurationAbbreviated('64.2134111s')).toBe('1m, 4s'); + }); + it('should return duration abbreviated', () => { + expect(formatDurationAbbreviated('2652361s')).toBe('30d, 16h, 46m, 1s'); + }); + it('should return duration abbreviated', () => { + expect(formatDurationAbbreviated('2694361s')).toBe('1month, 4h, 26m, 1s'); + }); + it('should return duration abbreviated', () => { + expect(formatDurationAbbreviated('32694361s')).toBe( + '1year, 13d, 9h, 46m, 1s', + ); + }); + }); +}); + +describe('formatSecondsAbbreviated', () => { + it('should return "13m 20s" for 800 seconds', () => { + expect(formatSecondsAbbreviated(800)).toBe('13m 20s'); + expect(formatSecondsAbbreviated('800')).toBe('13m 20s'); + }); + it('should return "1ms" for 0.001 seconds', () => { + expect(formatSecondsAbbreviated(0.001)).toBe('1ms'); + expect(formatSecondsAbbreviated('0.001')).toBe('1ms'); + }); }); diff --git a/src/lib/utilities/format-time.ts b/src/lib/utilities/format-time.ts index 6b0755d0e..bd0ddf6bb 100644 --- a/src/lib/utilities/format-time.ts +++ b/src/lib/utilities/format-time.ts @@ -1,11 +1,13 @@ import { - parseJSON, formatDuration as durationToString, - intervalToDuration, getMilliseconds as getSecondAsMilliseconds, + intervalToDuration, + parseJSON, } from 'date-fns'; -import type { Timestamp } from '$types'; +import type { Timestamp } from '$lib/types'; + +import { has } from './has'; import { fromSeconds } from './to-duration'; export type ValidTime = Parameters[0] | Timestamp; @@ -15,16 +17,16 @@ export function timestampToDate(ts: Timestamp): Date { throw new TypeError('provided value is not a timestamp'); } - const d = new Date(null); + const d = new Date(); - d.setTime(Number(ts.seconds) * 1000 + ts.nanos / 1000); + d.setTime(Number(ts.seconds) * 1000 + (ts.nanos || 0) / 1000000); return d; } export function isTimestamp(arg: unknown): arg is Timestamp { if (typeof arg === 'object') { - return arg['seconds'] !== undefined && arg['nanos'] !== undefined; + return has(arg, 'seconds') && has(arg, 'nanos'); } return false; } @@ -34,13 +36,23 @@ export function formatDuration( delimiter = ', ', ): string { if (duration === null || !duration) return ''; - if (typeof duration === 'string') duration = fromSeconds(duration); + if (typeof duration === 'string') return fromSeconds(duration, { delimiter }); return durationToString(duration, { delimiter }); } +export function formatDurationAbbreviated( + duration: Duration | string, + delimiter?: string, +): string { + return formatDistanceToSingleLetters(formatDuration(duration, delimiter)); +} + function formatDistanceToSingleLetters(distance: string) { if (!distance) return ''; distance = distance + .replace(/milliseconds/g, 'ms') + .replace(/millisecond/g, 'ms') + .replace(/ ms/g, 'ms') .replace(/seconds/g, 'second') .replace(/second/g, 's') .replace(/ s/g, 's') @@ -91,27 +103,82 @@ export function getDuration({ } } +export function getMillisecondDuration({ + start, + end, + onlyUnderSecond = true, +}: { + start: ValidTime | undefined | null; + end: ValidTime | undefined | null; + onlyUnderSecond?: boolean; +}): number | null { + if (!start || !end) return null; + + try { + if (isTimestamp(start)) { + start = timestampToDate(start); + } + + if (isTimestamp(end)) { + end = timestampToDate(end); + } + + const parsedStart = parseJSON(start); + const parsedEnd = parseJSON(end); + const ms = parsedEnd.getTime() - parsedStart.getTime(); + return onlyUnderSecond ? Math.abs(ms % 1000) : Math.abs(ms); + } catch { + return null; + } +} + export function formatDistance({ start, end, + includeMilliseconds = false, + includeMillisecondsForUnderSecond = false, }: { start: ValidTime | undefined | null; end: ValidTime | undefined | null; + includeMilliseconds?: boolean; + includeMillisecondsForUnderSecond?: boolean; }): string { const duration = getDuration({ start, end }); - return formatDuration(duration); + const distance = formatDuration(duration); + const msDuration = getMillisecondDuration({ start, end }); + + if (!distance && msDuration && includeMillisecondsForUnderSecond) { + return `${msDuration}ms`.trim(); + } else if (includeMilliseconds && msDuration) { + return `${distance} ${msDuration}ms`.trim(); + } else { + return distance; + } } export function formatDistanceAbbreviated({ start, end, + includeMilliseconds = false, + includeMillisecondsForUnderSecond = false, }: { start: ValidTime | undefined | null; end: ValidTime | undefined | null; + includeMilliseconds?: boolean; + includeMillisecondsForUnderSecond?: boolean; }): string { const duration = getDuration({ start, end }); const distance = formatDuration(duration, ' '); - return formatDistanceToSingleLetters(distance); + const formattedDistance = formatDistanceToSingleLetters(distance); + const msDuration = getMillisecondDuration({ start, end }); + + if (!distance && msDuration && includeMillisecondsForUnderSecond) { + return `${msDuration}ms`.trim(); + } else if (includeMilliseconds && msDuration) { + return `${formattedDistance} ${msDuration}ms`.trim(); + } else { + return formattedDistance; + } } export function getMilliseconds(date: ValidTime | undefined | null): number { @@ -126,9 +193,11 @@ export function getMilliseconds(date: ValidTime | undefined | null): number { export function fromSecondsToMinutesAndSeconds(seconds: number): string { if (!seconds) return ''; + const start = new Date(Date.UTC(0, 0, 0, 0, 0, 0)); + const end = new Date(Date.UTC(0, 0, 0, 0, 0, Math.floor(seconds))); const duration = intervalToDuration({ - start: 0, - end: Math.floor(seconds) * 1000, + start, + end, }); return durationToString(duration, { format: ['minutes', 'seconds'] }); } @@ -164,3 +233,9 @@ export const getTimestampDifference = ( const parse2 = Date.parse(date2); return Math.abs(parse1 - parse2); }; + +export const formatSecondsAbbreviated = (seconds: number | string): string => { + const start = new Date(); + const end = new Date(start.getTime() + Number(seconds) * 1000); + return formatDistanceAbbreviated({ start, end, includeMilliseconds: true }); +}; diff --git a/src/lib/utilities/get-api-origin.test.ts b/src/lib/utilities/get-api-origin.test.ts index 7b071b227..abe3e151f 100644 --- a/src/lib/utilities/get-api-origin.test.ts +++ b/src/lib/utilities/get-api-origin.test.ts @@ -1,4 +1,5 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; + import { getApiOrigin } from './get-api-origin'; describe('getApiOrigin', () => { diff --git a/src/lib/utilities/get-api-origin.ts b/src/lib/utilities/get-api-origin.ts index eb3b911f1..3747b4cdb 100644 --- a/src/lib/utilities/get-api-origin.ts +++ b/src/lib/utilities/get-api-origin.ts @@ -1,6 +1,6 @@ -import { browser } from '$app/env'; +import { BROWSER } from 'esm-env'; -export function getApiOrigin(isBrowser = browser): string | null { +export function getApiOrigin(isBrowser = BROWSER): string | null { const endpoint = import.meta.env.VITE_API; const isRelative = !endpoint.startsWith('http'); diff --git a/src/lib/utilities/get-clusters.test.ts b/src/lib/utilities/get-clusters.test.ts index b3f5488ec..bc2b1d0d3 100644 --- a/src/lib/utilities/get-clusters.test.ts +++ b/src/lib/utilities/get-clusters.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { getClusters } from './get-clusters'; const getNamespace = (clusters: string[], activeClusterName: string) => ({ diff --git a/src/lib/utilities/get-clusters.ts b/src/lib/utilities/get-clusters.ts index a90ffd85b..e228773eb 100644 --- a/src/lib/utilities/get-clusters.ts +++ b/src/lib/utilities/get-clusters.ts @@ -1,4 +1,4 @@ -import type { DescribeNamespaceResponse } from '$types'; +import type { DescribeNamespaceResponse } from '$lib/types'; export const getClusters = (namespace: DescribeNamespaceResponse): string => { const clusters = namespace?.replicationConfig?.clusters; diff --git a/src/lib/utilities/get-codec.test.ts b/src/lib/utilities/get-codec.test.ts new file mode 100644 index 000000000..00ea0e44b --- /dev/null +++ b/src/lib/utilities/get-codec.test.ts @@ -0,0 +1,258 @@ +import { beforeEach, describe, expect, it } from 'vitest'; + +import { + codecEndpoint, + includeCredentials, + overrideRemoteCodecConfiguration, + passAccessToken, +} from '$lib/stores/data-encoder-config'; +import type { Settings } from '$lib/types/global'; + +import { + getCodecEndpoint, + getCodecIncludeCredentials, + getCodecPassAccessToken, +} from './get-codec'; + +const defaultSettings = { + auth: { + enabled: false, + options: null, + }, + bannerText: '', + defaultNamespace: '', + showTemporalSystemNamespace: false, + feedbackURL: '', + notifyOnNewVersion: false, + codec: { + endpoint: '', + passAccessToken: false, + includeCredentials: false, + }, + version: '2.15.0', + disableWriteActions: false, + workflowTerminateDisabled: false, + workflowCancelDisabled: false, + workflowSignalDisabled: false, + workflowUpdateDisabled: false, + workflowResetDisabled: false, + batchActionsDisabled: false, +}; + +const getSettings = (customSettings: Partial = {}): Settings => { + return { ...defaultSettings, ...customSettings }; +}; + +const clearLocalStorageAndStores = () => { + localStorage.clear(); + passAccessToken.set(false); + codecEndpoint.set(null); + includeCredentials.set(false); + overrideRemoteCodecConfiguration.set(false); +}; + +describe('getCodec with only configuration settings', () => { + beforeEach(() => { + clearLocalStorageAndStores(); + }); + + it('should return codec endpoint from settings', () => { + const settings = getSettings({ + codec: { endpoint: 'https://mycodecserver.dev' }, + }); + const endpoint = getCodecEndpoint(settings); + const passAccessToken = getCodecPassAccessToken(settings); + const includeCredentials = getCodecIncludeCredentials(settings); + expect(endpoint).toEqual('https://mycodecserver.dev'); + expect(passAccessToken).toEqual(false); + expect(includeCredentials).toEqual(false); + }); + + it('should return codec endpoint and access token from settings', () => { + const settings = getSettings({ + codec: { + endpoint: 'https://mycodecserver.dev', + passAccessToken: true, + includeCredentials: false, + }, + }); + const endpoint = getCodecEndpoint(settings); + const passAccessToken = getCodecPassAccessToken(settings); + const includeCredentials = getCodecIncludeCredentials(settings); + expect(endpoint).toEqual('https://mycodecserver.dev'); + expect(passAccessToken).toEqual(true); + expect(includeCredentials).toEqual(false); + }); + + it('should return codec endpoint and include credentials from settings', () => { + const settings = getSettings({ + codec: { + endpoint: 'https://mycodecserver.dev', + passAccessToken: false, + includeCredentials: true, + }, + }); + const endpoint = getCodecEndpoint(settings); + const passAccessToken = getCodecPassAccessToken(settings); + const includeCredentials = getCodecIncludeCredentials(settings); + expect(endpoint).toEqual('https://mycodecserver.dev'); + expect(passAccessToken).toEqual(false); + expect(includeCredentials).toEqual(true); + }); +}); + +describe('getCodec with configuration settings and local settings but override off', () => { + beforeEach(() => { + clearLocalStorageAndStores(); + }); + + it('should return codec endpoint from settings with local setting endpoint set', () => { + codecEndpoint.set('http://mylocalserver.dev'); + const settings = getSettings({ + codec: { + endpoint: 'https://mycodecserver.dev', + passAccessToken: false, + includeCredentials: false, + }, + }); + const endpoint = getCodecEndpoint(settings); + const passAccessToken = getCodecPassAccessToken(settings); + const includeCredentials = getCodecIncludeCredentials(settings); + expect(endpoint).toEqual('https://mycodecserver.dev'); + expect(passAccessToken).toEqual(false); + expect(includeCredentials).toEqual(false); + }); + + it('should return codec endpoint from settings and with local setting endpoint and both options set', () => { + codecEndpoint.set('http://mylocalserver.dev'); + passAccessToken.set(true); + includeCredentials.set(true); + const settings = getSettings({ + codec: { + endpoint: 'https://mycodecserver.dev', + passAccessToken: false, + includeCredentials: false, + }, + }); + const endpoint = getCodecEndpoint(settings); + const token = getCodecPassAccessToken(settings); + const credentials = getCodecIncludeCredentials(settings); + expect(endpoint).toEqual('https://mycodecserver.dev'); + expect(token).toEqual(false); + expect(credentials).toEqual(false); + }); + + it('should return codec endpoint from settings and with local settings with one option set', () => { + codecEndpoint.set('http://mylocalserver.dev'); + passAccessToken.set(true); + includeCredentials.set(false); + const settings = getSettings({ + codec: { + endpoint: 'https://mycodecserver.dev', + passAccessToken: false, + includeCredentials: true, + }, + }); + const endpoint = getCodecEndpoint(settings); + const token = getCodecPassAccessToken(settings); + const credentials = getCodecIncludeCredentials(settings); + expect(endpoint).toEqual('https://mycodecserver.dev'); + expect(token).toEqual(false); + expect(credentials).toEqual(true); + }); + + it('should return codec endpoint from local setting with no settings', () => { + codecEndpoint.set('http://mylocalserver.dev'); + passAccessToken.set(true); + includeCredentials.set(false); + const settings = getSettings(); + const endpoint = getCodecEndpoint(settings); + const token = getCodecPassAccessToken(settings); + const credentials = getCodecIncludeCredentials(settings); + expect(endpoint).toEqual('http://mylocalserver.dev'); + expect(token).toEqual(true); + expect(credentials).toEqual(false); + }); +}); + +describe('getCodec with configuration settings and local settings but override on', () => { + beforeEach(() => { + clearLocalStorageAndStores(); + }); + + it('should return codec endpoint from local setting', () => { + codecEndpoint.set('http://mylocalserver.dev'); + overrideRemoteCodecConfiguration.set(true); + const settings = getSettings({ + codec: { + endpoint: 'https://mycodecserver.dev', + passAccessToken: false, + includeCredentials: false, + }, + }); + const endpoint = getCodecEndpoint(settings); + const token = getCodecPassAccessToken(settings); + + const credentials = getCodecIncludeCredentials(settings); + expect(endpoint).toEqual('http://mylocalserver.dev'); + expect(token).toEqual(false); + expect(credentials).toEqual(false); + }); + + it('should return codec endpoint from local setting with options', () => { + codecEndpoint.set('http://mylocalserver.dev'); + passAccessToken.set(false); + includeCredentials.set(true); + overrideRemoteCodecConfiguration.set(true); + + const settings = getSettings({ + codec: { + endpoint: 'https://mycodecserver.dev', + passAccessToken: true, + includeCredentials: true, + }, + }); + const endpoint = getCodecEndpoint(settings); + const token = getCodecPassAccessToken(settings); + + const credentials = getCodecIncludeCredentials(settings); + expect(endpoint).toEqual('http://mylocalserver.dev'); + expect(token).toEqual(false); + expect(credentials).toEqual(true); + }); +}); + +describe('getCodec with configuration settings and local settings after multiple updates', () => { + beforeEach(() => { + clearLocalStorageAndStores(); + }); + + it('Get settings without local settings, then add local settings with override, then remove override', () => { + const settings = getSettings({ + codec: { + endpoint: 'https://mycodecserver.dev', + passAccessToken: true, + includeCredentials: false, + }, + }); + + expect(getCodecEndpoint(settings)).toEqual('https://mycodecserver.dev'); + expect(getCodecPassAccessToken(settings)).toEqual(true); + expect(getCodecIncludeCredentials(settings)).toEqual(false); + + codecEndpoint.set('http://mylocalserver.dev'); + passAccessToken.set(false); + includeCredentials.set(false); + overrideRemoteCodecConfiguration.set(true); + + expect(getCodecEndpoint(settings)).toEqual('http://mylocalserver.dev'); + expect(getCodecPassAccessToken(settings)).toEqual(false); + expect(getCodecIncludeCredentials(settings)).toEqual(false); + + overrideRemoteCodecConfiguration.set(false); + + expect(getCodecEndpoint(settings)).toEqual('https://mycodecserver.dev'); + expect(getCodecPassAccessToken(settings)).toEqual(true); + expect(getCodecIncludeCredentials(settings)).toEqual(false); + }); +}); diff --git a/src/lib/utilities/get-codec.ts b/src/lib/utilities/get-codec.ts index eae536dbf..d45d19306 100644 --- a/src/lib/utilities/get-codec.ts +++ b/src/lib/utilities/get-codec.ts @@ -1,19 +1,44 @@ import { get } from 'svelte/store'; + import { codecEndpoint, + includeCredentials, + overrideRemoteCodecConfiguration, passAccessToken, } from '$lib/stores/data-encoder-config'; +import type { Settings } from '$lib/types/global'; export const getCodecEndpoint = ( settings: Settings, endpoint = codecEndpoint, + override = overrideRemoteCodecConfiguration, ): string => { - return get(endpoint) || settings?.codec?.endpoint || ''; + const overrideEndpoint = get(override) && get(endpoint); + return overrideEndpoint || settings?.codec?.endpoint || get(endpoint) || ''; }; export const getCodecPassAccessToken = ( settings: Settings, passToken = passAccessToken, + endpoint = codecEndpoint, + override = overrideRemoteCodecConfiguration, +): boolean => { + const overrideEndpoint = get(override) && get(endpoint); + const nonOverrideOption = settings?.codec?.endpoint + ? !!settings?.codec?.passAccessToken + : !!get(passToken); + return overrideEndpoint ? !!get(passToken) : nonOverrideOption; +}; + +export const getCodecIncludeCredentials = ( + settings: Settings, + includeCreds = includeCredentials, + endpoint = codecEndpoint, + override = overrideRemoteCodecConfiguration, ): boolean => { - return get(passToken) || settings?.codec?.passAccessToken; + const overrideEndpoint = get(override) && get(endpoint); + const nonOverrideOption = settings?.codec?.endpoint + ? !!settings?.codec?.includeCredentials + : !!get(includeCreds); + return overrideEndpoint ? !!get(includeCreds) : nonOverrideOption; }; diff --git a/src/lib/utilities/get-deployment-build-id.test.ts b/src/lib/utilities/get-deployment-build-id.test.ts new file mode 100644 index 000000000..78515072a --- /dev/null +++ b/src/lib/utilities/get-deployment-build-id.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from 'vitest'; + +import { + getBuildIdFromVersion, + getDeploymentFromVersion, +} from './get-deployment-build-id'; + +describe('getBuildIdFromVersion', () => { + it('should return empty string with no version', () => { + expect(getBuildIdFromVersion(undefined)).toEqual(''); + }); + it('should return empty string with empty string version', () => { + expect(getBuildIdFromVersion('')).toEqual(''); + }); + it('should return build id with . delimiter', () => { + expect(getBuildIdFromVersion('orders.v12')).toEqual('v12'); + }); + it('should return build id with : delimiter', () => { + expect(getBuildIdFromVersion('orders:v12')).toEqual('v12'); + }); + it('should return version string with invalid delimiter', () => { + expect(getBuildIdFromVersion('orders/v12')).toEqual('orders/v12'); + }); + it('should return version string with __unversioned__', () => { + expect(getBuildIdFromVersion('__unversioned__')).toEqual('__unversioned__'); + }); +}); + +describe('getDeploymentFromVersion', () => { + it('should return empty string with no version', () => { + expect(getDeploymentFromVersion(undefined)).toEqual(''); + }); + it('should return empty string with empty string version', () => { + expect(getDeploymentFromVersion('')).toEqual(''); + }); + it('should return deployment with . delimiter', () => { + expect(getDeploymentFromVersion('orders.v12')).toEqual('orders'); + }); + it('should return deployment with : delimiter', () => { + expect(getDeploymentFromVersion('orders:v12')).toEqual('orders'); + }); + it('should return version string with invalid delimiter', () => { + expect(getDeploymentFromVersion('orders/v12')).toEqual('orders/v12'); + }); + it('should return version string with __unversioned__', () => { + expect(getDeploymentFromVersion('__unversioned__')).toEqual( + '__unversioned__', + ); + }); +}); diff --git a/src/lib/utilities/get-deployment-build-id.ts b/src/lib/utilities/get-deployment-build-id.ts new file mode 100644 index 000000000..16dbce057 --- /dev/null +++ b/src/lib/utilities/get-deployment-build-id.ts @@ -0,0 +1,31 @@ +import { + isVersionSummaryNew, + type VersionSummary, +} from '$lib/types/deployments'; + +export const getBuildIdFromVersion = (version: string | undefined): string => { + if (!version) return ''; + const newDelimiter = version.split(':')[1]; + if (newDelimiter) return newDelimiter; + const oldDelimiter = version.split('.')[1]; + if (oldDelimiter) return oldDelimiter; + return version; +}; + +export const getDeploymentFromVersion = ( + version: string | undefined, +): string => { + if (!version) return ''; + const newDelimiter = version.split(':'); + if (newDelimiter && newDelimiter.length === 2) return newDelimiter[0]; + const oldDelimiter = version.split('.'); + if (oldDelimiter && oldDelimiter.length === 2) return oldDelimiter[0]; + return version; +}; + +export const getDeploymentVersionFromStruct = (version: VersionSummary) => { + if (isVersionSummaryNew(version) && version.deploymentVersion) { + return `${version.deploymentVersion.deploymentName}:${version.deploymentVersion.buildId}`; + } + return `${version.version}`; +}; diff --git a/src/lib/utilities/get-failed-or-pending.test.ts b/src/lib/utilities/get-failed-or-pending.test.ts new file mode 100644 index 000000000..a246822c6 --- /dev/null +++ b/src/lib/utilities/get-failed-or-pending.test.ts @@ -0,0 +1,129 @@ +import { describe, expect, it } from 'vitest'; + +import { + getFailedOrPendingEvents, + getFailedOrPendingGroups, +} from './get-failed-or-pending'; + +const completedEvent = { + classification: 'Completed', + eventType: 'test', +}; +const failedEvent = { + classification: 'Failed', + eventType: 'test', +}; +const timedOutEvent = { + classification: 'TimedOut', + eventType: 'test', +}; +const canceledEvent = { + classification: 'Canceled', + eventType: 'test', +}; +const terminatedEvent = { + classification: 'Terminated', + eventType: 'test', +}; +const pendingActivity = { + activityType: { name: 'test' }, +}; +const failedLocalEvent = { + eventType: 'test', + markerRecordedEventAttributes: { + markerName: 'LocalActivity', + failure: true, + }, +}; + +const completedEventGroup = { + events: [completedEvent], + classification: 'Completed', + isPending: false, +}; +const failedEventGroup = { + events: [failedEvent], + classification: 'Failed', + isPending: false, +}; +const pendingEventGroup = { + events: [failedEvent], + classification: 'Failed', + isPending: true, +}; + +describe('getFailedOrPendingEvents', () => { + it('should return all items if not filtering for failed event', () => { + const filteredEvents = getFailedOrPendingEvents( + [completedEvent, failedEvent], + false, + ); + expect(filteredEvents.length).toEqual(2); + }); + it('should return no items if no failed/timedout/pending events', () => { + const filteredEvents = getFailedOrPendingEvents( + [completedEvent, canceledEvent, terminatedEvent], + true, + ); + expect(filteredEvents.length).toEqual(0); + }); + it('should return filtered items for failed event', () => { + const filteredEvents = getFailedOrPendingEvents( + [completedEvent, failedEvent], + true, + ); + expect(filteredEvents.length).toEqual(1); + }); + it('should return filtered items for timed out event', () => { + const filteredEvents = getFailedOrPendingEvents( + [completedEvent, timedOutEvent], + true, + ); + expect(filteredEvents.length).toEqual(1); + }); + it('should return filtered items for pending events', () => { + const filteredEvents = getFailedOrPendingEvents( + [completedEvent, pendingActivity, pendingActivity], + true, + ); + expect(filteredEvents.length).toEqual(2); + }); + it('should return filtered items for failed local event', () => { + const filteredEvents = getFailedOrPendingEvents( + [completedEvent, failedLocalEvent], + true, + ); + expect(filteredEvents.length).toEqual(1); + }); +}); + +describe('getFailedOrPendingGroups', () => { + it('should return all items if not filtering for failed group', () => { + const filteredGroups = getFailedOrPendingGroups( + [completedEventGroup, completedEventGroup], + false, + ); + expect(filteredGroups.length).toEqual(2); + }); + it('should return no items if no failed/canceled/pending group', () => { + const filteredGroups = getFailedOrPendingGroups( + [completedEventGroup, completedEventGroup], + true, + ); + expect(filteredGroups.length).toEqual(0); + }); + it('should return filtered items for failed group', () => { + const filteredGroups = getFailedOrPendingGroups( + [completedEventGroup, failedEventGroup], + true, + ); + expect(filteredGroups.length).toEqual(1); + }); + it('should return filtered items for pending group', () => { + const filteredGroups = getFailedOrPendingGroups( + [completedEventGroup, pendingEventGroup], + true, + ); + expect(filteredGroups.length).toEqual(1); + }); +}); diff --git a/src/lib/utilities/get-failed-or-pending.ts b/src/lib/utilities/get-failed-or-pending.ts new file mode 100644 index 000000000..f7f68fdbe --- /dev/null +++ b/src/lib/utilities/get-failed-or-pending.ts @@ -0,0 +1,74 @@ +import { isEventGroup } from '$lib/models/event-groups'; +import type { EventGroup } from '$lib/models/event-groups/event-groups'; +import { isEvent } from '$lib/models/event-history'; +import type { + IterableEventWithPending, + WorkflowEvent, +} from '$lib/types/events'; + +import { isLocalActivityMarkerEvent } from './is-event-type'; +import { + isPendingActivity, + isPendingNexusOperation, +} from './is-pending-activity'; + +const isFailedLocalActivity = (event: WorkflowEvent) => { + return ( + isLocalActivityMarkerEvent(event) && + event.markerRecordedEventAttributes.failure + ); +}; + +const isFailedLocalActivityGroup = (group: EventGroup) => { + return isFailedLocalActivity(group.initialEvent); +}; + +const isFailedEvent = (iterable: IterableEventWithPending) => { + return ( + isEvent(iterable) && + (iterable.classification === 'Failed' || + iterable.classification === 'TimedOut' || + isFailedLocalActivity(iterable)) + ); +}; + +const isPendingEvent = (iterable: IterableEventWithPending) => { + return isPendingActivity(iterable) || isPendingNexusOperation(iterable); +}; + +const isFailedEventGroup = (iterable: IterableEventWithPending) => { + return ( + isEventGroup(iterable) && + (iterable.classification === 'Failed' || + iterable.classification === 'TimedOut' || + isFailedLocalActivityGroup(iterable)) + ); +}; + +const isPendingEventGroup = (iterable: IterableEventWithPending) => { + return isEventGroup(iterable) && iterable.isPending; +}; + +export const getFailedOrPendingEvents = ( + items: IterableEventWithPending[], + filterForFailedOrPending: boolean, +) => { + if (!filterForFailedOrPending) return items; + return items.filter( + (item) => + isFailedEvent(item) || + isPendingEvent(item) || + isFailedEventGroup(item) || + isPendingEventGroup(item), + ); +}; + +export const getFailedOrPendingGroups = ( + items: EventGroup[], + filterForFailedOrPending: boolean, +) => { + if (!filterForFailedOrPending) return items; + return items.filter( + (item) => isFailedEventGroup(item) || isPendingEventGroup(item), + ); +}; diff --git a/src/lib/utilities/get-float-style.test.ts b/src/lib/utilities/get-float-style.test.ts index 4f7aa2f25..a90577b3c 100644 --- a/src/lib/utilities/get-float-style.test.ts +++ b/src/lib/utilities/get-float-style.test.ts @@ -1,11 +1,12 @@ import { describe, expect, it } from 'vitest'; + import { getFloatStyle } from './get-float-style'; describe('getFloatStyle', () => { it('should return a style string for a width/height and screenWidth over default breakpoint', () => { const dimensions = { width: 390, - height: 82, + height: 96, screenWidth: 1400, }; const style = 'position: absolute; right: 410px; top: -96px'; @@ -14,7 +15,7 @@ describe('getFloatStyle', () => { it('should return a style string for a width/height and screenWidth over custom breakpoint', () => { const dimensions = { width: 390, - height: 82, + height: 96, screenWidth: 1400, breakpoint: 900, }; diff --git a/src/lib/utilities/get-float-style.ts b/src/lib/utilities/get-float-style.ts index 3d2b828aa..021cec7cb 100644 --- a/src/lib/utilities/get-float-style.ts +++ b/src/lib/utilities/get-float-style.ts @@ -2,14 +2,14 @@ export const getFloatStyle = ({ width, height, screenWidth, - breakpoint = 1024, + breakpoint = 1279, // 'xl' max-width breakpoint in tailwindcss }: { width?: number; height?: number; - screenWidth?: number; + screenWidth: number; breakpoint?: number; }): string => { return width && height && screenWidth > breakpoint - ? `position: absolute; right: ${width + 20}px; top: -${height + 14}px` + ? `position: absolute; right: ${width + 20}px; top: -${height}px` : ''; }; diff --git a/src/lib/utilities/get-group-status-and-count.ts b/src/lib/utilities/get-group-status-and-count.ts new file mode 100644 index 000000000..9541c70bd --- /dev/null +++ b/src/lib/utilities/get-group-status-and-count.ts @@ -0,0 +1,23 @@ +import { workflowStatuses } from '$lib/models/workflow-status'; +import type { WorkflowStatus } from '$lib/types/workflows'; + +import { decodePayload } from './decode-payload'; + +export const getStatusAndCountOfGroup = (groups = []) => { + return groups + .map((group) => { + const status = decodePayload( + group?.groupValues[0], + ) as unknown as WorkflowStatus; + const count = parseInt(group.count); + return { + status, + count, + }; + }) + .sort((a, b) => { + return ( + workflowStatuses.indexOf(a.status) - workflowStatuses.indexOf(b.status) + ); + }); +}; diff --git a/src/lib/utilities/get-namespace.test.ts b/src/lib/utilities/get-namespace.test.ts index 4a7a4ba43..d3fa9d03e 100644 --- a/src/lib/utilities/get-namespace.test.ts +++ b/src/lib/utilities/get-namespace.test.ts @@ -1,37 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { getNamespace } from './get-namespace'; -const temporalSystemNamespace = { - namespaceInfo: { - name: 'temporal-system', - state: 'Registered', - description: 'Temporal internal system namespace', - ownerEmail: 'temporal-core@temporal.io', - data: {}, - id: '32049b68-7872-4094-8e63-d0dd59896a83', - }, - config: { - workflowExecutionRetentionTtl: '604800s', - badBinaries: { - binaries: {}, - }, - historyArchivalState: 'Disabled', - historyArchivalUri: '', - visibilityArchivalState: 'Disabled', - visibilityArchivalUri: '', - }, - replicationConfig: { - activeClusterName: 'active', - clusters: [ - { - clusterName: 'active', - }, - ], - state: 'Unspecified', - }, - failoverVersion: '0', - isGlobalNamespace: false, -}; +import { getNamespace } from './get-namespace'; const canaryNamespace = { namespaceInfo: { @@ -97,20 +66,6 @@ const defaultNamespace = { isGlobalNamespace: false, }; -const getSettings = ( - defaultNamespace: string, - showTemporalSystemNamespace: boolean, -) => { - return { - auth: { - enabled: false, - options: ['organization', 'invitation', 'audience'], - }, - defaultNamespace, - showTemporalSystemNamespace, - }; -}; - describe('getNamespace', () => { it('should return default namespace if no namespace given', () => { const namespaces = [defaultNamespace, canaryNamespace]; diff --git a/src/lib/utilities/get-namespace.ts b/src/lib/utilities/get-namespace.ts index 8a4903fb7..b2089c396 100644 --- a/src/lib/utilities/get-namespace.ts +++ b/src/lib/utilities/get-namespace.ts @@ -1,4 +1,4 @@ -import type { DescribeNamespaceResponse } from '$types'; +import type { DescribeNamespaceResponse } from '$lib/types'; type GetNamespaceParameters = { namespace?: string; diff --git a/src/lib/utilities/get-public-path.ts b/src/lib/utilities/get-public-path.ts deleted file mode 100644 index ab0311279..000000000 --- a/src/lib/utilities/get-public-path.ts +++ /dev/null @@ -1 +0,0 @@ -export const publicPath: string = import.meta.env.VITE_PUBLIC_PATH || ''; diff --git a/src/lib/utilities/get-query-types-from-error.test.ts b/src/lib/utilities/get-query-types-from-error.test.ts index da8d26484..f1c306388 100644 --- a/src/lib/utilities/get-query-types-from-error.test.ts +++ b/src/lib/utilities/get-query-types-from-error.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { getQueryTypesFromError } from './get-query-types-from-error'; const error = { @@ -29,22 +30,22 @@ const javaSDKError = { describe('getQueryTypesFromError', () => { it('should return an array of query types', () => { - const queryTypes = ['__open_sessions']; + const queryTypes = [{ name: '__open_sessions' }]; expect(getQueryTypesFromError(error.message)).toEqual(queryTypes); }); it('should omit __stack_traces', () => { - expect(getQueryTypesFromError(error.message)).not.toContain( - '__stack_trace', - ); + expect(getQueryTypesFromError(error.message)).not.toContain({ + name: '__stack_trace', + }); }); it('should work with the Java SDK', () => { expect(getQueryTypesFromError(javaSDKError.message)).toEqual([ - 'getLoan', - 'getNextPaymentDue', - 'getPaymentObligations', - 'getNextPaymentObligation', + { name: 'getLoan' }, + { name: 'getNextPaymentDue' }, + { name: 'getPaymentObligations' }, + { name: 'getNextPaymentObligation' }, ]); }); }); diff --git a/src/lib/utilities/get-query-types-from-error.ts b/src/lib/utilities/get-query-types-from-error.ts index d6fddf2d2..3016299da 100644 --- a/src/lib/utilities/get-query-types-from-error.ts +++ b/src/lib/utilities/get-query-types-from-error.ts @@ -1,4 +1,4 @@ -export const getQueryTypesFromError = (message: string): string[] => { +export const getQueryTypesFromError = (message: string): { name: string }[] => { const indexOfOpeningBracket = message.indexOf('['); const indexOfClosingBracket = message.indexOf(']'); @@ -8,9 +8,9 @@ export const getQueryTypesFromError = (message: string): string[] => { .filter((query: string) => query !== '__stack_trace') .map((query: string) => { if (query.endsWith(',')) { - return query.slice(0, query.length - 1); + return { name: query.slice(0, query.length - 1) }; } else { - return query; + return { name: query }; } }); }; diff --git a/src/lib/utilities/get-sdk-version.test.ts b/src/lib/utilities/get-sdk-version.test.ts new file mode 100644 index 000000000..6ac02d410 --- /dev/null +++ b/src/lib/utilities/get-sdk-version.test.ts @@ -0,0 +1,208 @@ +import { describe, expect, it } from 'vitest'; + +import { getSDKandVersion } from './get-sdk-version'; + +describe('getSDKandVersion', () => { + const workflowTaskCompletedEvent = { + attributes: { + sdkMetadata: {}, + }, + }; + it('should return empty string if neither exist in task', () => { + const sdk = getSDKandVersion([workflowTaskCompletedEvent]).sdk; + const version = getSDKandVersion([workflowTaskCompletedEvent]).version; + expect(sdk).toBe(''); + expect(version).toBe(''); + }); + + it('should return Go sdk and version if both exist in task', () => { + const workflowTaskCompletedEvent = { + attributes: { + sdkMetadata: { + sdkName: 'temporal-go', + sdkVersion: '1.29.1', + }, + }, + }; + + const sdk = getSDKandVersion([workflowTaskCompletedEvent]).sdk; + const version = getSDKandVersion([workflowTaskCompletedEvent]).version; + expect(sdk).toBe('Go'); + expect(version).toBe('1.29.1'); + }); + + it('should return Java sdk and version if both exist in task', () => { + const workflowTaskCompletedEvent = { + attributes: { + sdkMetadata: { + sdkName: 'temporal-java', + sdkVersion: '1.0.5', + }, + }, + }; + + const sdk = getSDKandVersion([workflowTaskCompletedEvent]).sdk; + const version = getSDKandVersion([workflowTaskCompletedEvent]).version; + expect(sdk).toBe('Java'); + expect(version).toBe('1.0.5'); + }); + + it('should return Typescript sdk and version if both exist in task', () => { + const workflowTaskCompletedEvent = { + attributes: { + sdkMetadata: { + sdkName: 'temporal-typescript', + sdkVersion: '2.20.5', + }, + }, + }; + + const sdk = getSDKandVersion([workflowTaskCompletedEvent]).sdk; + const version = getSDKandVersion([workflowTaskCompletedEvent]).version; + expect(sdk).toBe('Typescript'); + expect(version).toBe('2.20.5'); + }); + + it('should return Dotnet sdk and version if both exist in task', () => { + const workflowTaskCompletedEvent = { + attributes: { + sdkMetadata: { + sdkName: 'temporal-dotnet', + sdkVersion: '2.20.5', + }, + }, + }; + + const sdk = getSDKandVersion([workflowTaskCompletedEvent]).sdk; + const version = getSDKandVersion([workflowTaskCompletedEvent]).version; + expect(sdk).toBe('.NET'); + expect(version).toBe('2.20.5'); + }); + + it('should return Python sdk and version if both exist in task', () => { + const workflowTaskCompletedEvent = { + attributes: { + sdkMetadata: { + sdkName: 'temporal-python', + sdkVersion: '0.2.11', + }, + }, + }; + + const sdk = getSDKandVersion([workflowTaskCompletedEvent]).sdk; + const version = getSDKandVersion([workflowTaskCompletedEvent]).version; + expect(sdk).toBe('Python'); + expect(version).toBe('0.2.11'); + }); + + it('should return Php sdk and version if both exist in task', () => { + const workflowTaskCompletedEvent = { + attributes: { + sdkMetadata: { + sdkName: 'temporal-php', + sdkVersion: '0.2.11', + }, + }, + }; + + const sdk = getSDKandVersion([workflowTaskCompletedEvent]).sdk; + const version = getSDKandVersion([workflowTaskCompletedEvent]).version; + expect(sdk).toBe('Php'); + expect(version).toBe('0.2.11'); + }); + + it('should return Rust sdk and version if both exist in task', () => { + const workflowTaskCompletedEvent = { + attributes: { + sdkMetadata: { + sdkName: 'temporal-rust', + sdkVersion: '1.3.2', + }, + }, + }; + + const sdk = getSDKandVersion([workflowTaskCompletedEvent]).sdk; + const version = getSDKandVersion([workflowTaskCompletedEvent]).version; + expect(sdk).toBe('Rust'); + expect(version).toBe('1.3.2'); + }); + + it('should return Ruby sdk and version if both exist in task', () => { + const workflowTaskCompletedEvent = { + attributes: { + sdkMetadata: { + sdkName: 'temporal-ruby', + sdkVersion: '1.3.21', + }, + }, + }; + + const sdk = getSDKandVersion([workflowTaskCompletedEvent]).sdk; + const version = getSDKandVersion([workflowTaskCompletedEvent]).version; + expect(sdk).toBe('Ruby'); + expect(version).toBe('1.3.21'); + }); + + it('should return newer Go version with multiple tasks', () => { + const workflowTaskCompletedEvent1 = { + attributes: { + sdkMetadata: { + sdkName: 'temporal-go', + sdkVersion: '1.29.1', + }, + }, + }; + + const workflowTaskCompletedEvent2 = { + attributes: { + sdkMetadata: { + sdkVersion: '1.30.5', + }, + }, + }; + const events = [workflowTaskCompletedEvent1, workflowTaskCompletedEvent2]; + + const sdk = getSDKandVersion(events).sdk; + const version = getSDKandVersion(events).version; + expect(sdk).toBe('Go'); + expect(version).toBe('1.30.5'); + }); + + it('should return newer SDK version with multiple tasks', () => { + const workflowTaskCompletedEvent1 = { + attributes: { + sdkMetadata: { + sdkName: 'temporal-go', + sdkVersion: '1.29.1', + }, + }, + }; + + const workflowTaskCompletedEvent2 = { + attributes: { + sdkMetadata: { + sdkName: 'temporal-ruby', + sdkVersion: '1.2.3', + }, + }, + }; + + const workflowTaskCompletedEvent3 = { + attributes: { + sdkMetadata: { + sdkVersion: '1.2.10', + }, + }, + }; + const events = [ + workflowTaskCompletedEvent1, + workflowTaskCompletedEvent2, + workflowTaskCompletedEvent3, + ]; + + const sdk = getSDKandVersion(events).sdk; + const version = getSDKandVersion(events).version; + expect(sdk).toBe('Ruby'); + expect(version).toBe('1.2.10'); + }); +}); diff --git a/src/lib/utilities/get-sdk-version.ts b/src/lib/utilities/get-sdk-version.ts new file mode 100644 index 000000000..182ca4f94 --- /dev/null +++ b/src/lib/utilities/get-sdk-version.ts @@ -0,0 +1,31 @@ +import type { WorkflowTaskCompletedEvent } from '$lib/types/events'; + +import { capitalize } from './format-camel-case'; + +export const getSDKandVersion = ( + tasks: WorkflowTaskCompletedEvent[], +): { sdk: string; version: string } => { + let sdk = ''; + let version = ''; + + if (!tasks?.length) return { sdk, version }; + + tasks.forEach((event) => { + const sdkMetadata = event?.attributes?.sdkMetadata; + if (sdkMetadata) { + const sdkName = sdkMetadata?.sdkName; + const sdkVersion = sdkMetadata?.sdkVersion; + if (sdkName) { + sdk = capitalize(sdkName.split('-')[1]); + if (sdk === 'Dotnet') { + sdk = '.NET'; + } + } + if (sdkVersion) { + version = sdkVersion; + } + } + }); + + return { sdk, version }; +}; diff --git a/src/lib/utilities/get-single-attribute-for-event.test.ts b/src/lib/utilities/get-single-attribute-for-event.test.ts index 238a5b4fb..64b712728 100644 --- a/src/lib/utilities/get-single-attribute-for-event.test.ts +++ b/src/lib/utilities/get-single-attribute-for-event.test.ts @@ -1,10 +1,22 @@ import { describe, expect, it } from 'vitest'; + import { - getSingleAttributeForEvent, + getCodeBlockValue, + getPrimaryAttributeForEvent, + getSummaryAttribute, shouldDisplayAsExecutionLink, + shouldDisplayAsTime, + shouldDisplayAttribute, } from './get-single-attribute-for-event'; +import { toEvent } from '../models/event-history'; + +import dotnetLocalActivity from '$fixtures/local-activities/dotnet_local_activity.json'; +import goLocalActivity from '$fixtures/local-activities/go_local_activity.json'; +import javaLocalActivity from '$fixtures/local-activities/java_local_activity.json'; +import pythonLocalActivity from '$fixtures/local-activities/python_local_activity.json'; +import tsLocalActivity from '$fixtures/local-activities/ts_local_activity.json'; -describe('getSingleAttributeForEvent', () => { +describe('getPrimaryAttributeForEvent', () => { const workflowEvent = { eventId: '1', eventTime: '2022-03-14T17:44:14.996241458Z', @@ -110,34 +122,44 @@ describe('getSingleAttributeForEvent', () => { }, }; - it('should return "workflowType.name" if the workflow type exists', () => { - expect(getSingleAttributeForEvent(workflowEvent)).toStrictEqual({ + it('should return "workflowType.name" if the workflow type exists', async () => { + expect(await getPrimaryAttributeForEvent(workflowEvent)).toStrictEqual({ key: 'workflowTypeName', value: 'RainbowStatusesWorkflow', }); }); - it('should return "taskqueue.name" if the workflow type does not exists', () => { + it('should return "input" if the workflow type does not exists', async () => { const event = { ...workflowEvent }; event.attributes.workflowType = null; - expect(getSingleAttributeForEvent(event)).toStrictEqual({ - key: 'taskQueueName', - value: 'rainbow-statuses', + expect(await getPrimaryAttributeForEvent(event)).toStrictEqual({ + key: 'input', + value: { + payloads: [ + { + At: '2022-04-04T11:50:28.151785-05:00', + Hey: 'from Mars', + }, + ], + }, }); }); - it('should return "parentInitiatedEventId" if the workflow type and task queue does not exist', () => { + it('should return "taskQueue" if the workflow type and input does not exist', async () => { const event = { ...workflowEvent }; - event.attributes.workflowType = null; - event.attributes.taskQueue = null; - expect(getSingleAttributeForEvent(event)).toStrictEqual({ - key: 'parentInitiatedEventId', - value: '0', + event.attributes.workflowType = null as unknown as { + name: string; + }; + event.attributes.input = null; + + expect(await getPrimaryAttributeForEvent(event)).toStrictEqual({ + key: 'taskQueueName', + value: 'rainbow-statuses', }); }); - it('should return empty key value object if none of the attributes display', () => { - expect(getSingleAttributeForEvent(null)).toStrictEqual({ + it('should return empty key value object if none of the attributes display', async () => { + expect(await getPrimaryAttributeForEvent(null)).toStrictEqual({ key: '', value: '', }); @@ -158,3 +180,140 @@ describe('shouldDisplayAsExecutionLink', () => { expect(shouldDisplayAsExecutionLink('inlineRunIdSample')).toBe(false); }); }); + +describe('shouldDisplayAsTime', () => { + it('should return non-time values as is', () => { + expect(shouldDisplayAsTime('originalExecutionRunId')).toBe(false); + expect(shouldDisplayAsTime('lastScheduledTimeout')).toBe(false); + expect(shouldDisplayAsTime('timeToFire')).toBe(false); + }); + it('should return time values as formatted time', () => { + expect(shouldDisplayAsTime('lastScheduledTime')).toBe(true); + expect(shouldDisplayAsTime('expirationTime')).toBe(true); + expect(shouldDisplayAsTime('lastHeartbeatTime')).toBe(true); + }); +}); + +describe('shouldDisplayAttribute', () => { + it('should return false for certain attribute values', () => { + expect(shouldDisplayAttribute('', null)).toBe(false); + expect(shouldDisplayAttribute('', undefined)).toBe(false); + expect(shouldDisplayAttribute('', '')).toBe(false); + expect(shouldDisplayAttribute('', '0s')).toBe(false); + expect(shouldDisplayAttribute('type', '')).toBe(false); + expect(shouldDisplayAttribute('suggestContinueAsNew', false)).toBe(false); + expect(shouldDisplayAttribute('historySizeBytes', '0')).toBe(false); + }); + + it('should return false for certain attributes', () => { + expect(shouldDisplayAttribute('type', '')).toBe(false); + }); + + it('should return false for certain atrributes with unpopulated values', () => { + expect(shouldDisplayAttribute('suggestContinueAsNew', true)).toBe(true); + expect(shouldDisplayAttribute('suggestContinueAsNew', false)).toBe(false); + expect(shouldDisplayAttribute('historySizeBytes', '256')).toBe(true); + expect(shouldDisplayAttribute('historySizeBytes', '0')).toBe(false); + }); +}); + +describe('getCodeBlockValue', () => { + it('should return value if value is a string', () => { + expect(getCodeBlockValue('test')).toBe('test'); + }); + + it('should return payloads if they exist', () => { + expect(getCodeBlockValue({ payloads: [1, 2, 3] })).toEqual([1, 2, 3]); + expect(getCodeBlockValue({ payloads: null })).toBe(null); + }); + + it('should return indexedFields if they exist', () => { + expect(getCodeBlockValue({ indexedFields: [1, 2, 3] })).toEqual([1, 2, 3]); + expect(getCodeBlockValue({ indexedFields: null })).toBe(null); + }); + + it('should return points if they exist', () => { + expect(getCodeBlockValue({ points: [1, 2, 3] })).toEqual([1, 2, 3]); + expect(getCodeBlockValue({ points: null })).toBe(null); + }); + + it("should return the value if it is not a string and payloads, indexedFields, or points don't exist", () => { + expect(getCodeBlockValue({ test: [1, 2, 3] })).toEqual({ test: [1, 2, 3] }); + expect(getCodeBlockValue(0)).toBe(0); + expect(getCodeBlockValue(1)).toBe(1); + expect(getCodeBlockValue(null)).toBe(null); + expect(getCodeBlockValue([1, 2, 3])).toEqual([1, 2, 3]); + }); +}); + +describe('getSummaryEvent', () => { + it('should return expected payload for Go', async () => { + const localActivity = await toEvent(goLocalActivity); + expect(getSummaryAttribute(localActivity)).toStrictEqual({ + key: 'ActivityType', + value: 'Activity', + }); + }); + it('should return expected payload for TS', async () => { + const localActivity = await toEvent(tsLocalActivity); + expect(getSummaryAttribute(localActivity)).toStrictEqual({ + key: 'ActivityType', + value: 'greet', + }); + }); + it('should return expected payload for Java', async () => { + const localActivity = await toEvent(javaLocalActivity); + expect(getSummaryAttribute(localActivity)).toStrictEqual({ + key: 'ActivityType', + value: 'greet', + }); + }); + it('should return expected payload for Python', async () => { + const localActivity = await toEvent(pythonLocalActivity); + expect(getSummaryAttribute(localActivity)).toStrictEqual({ + key: 'ActivityType', + value: 'compose_greeting', + }); + }); + it('should return expected payload for .Net', async () => { + const localActivity = await toEvent(dotnetLocalActivity); + expect(getSummaryAttribute(localActivity)).toStrictEqual({ + key: 'ActivityType', + value: 'DoStaticThing', + }); + }); + + it('should return expected payload for a Nexus single payload', async () => { + const nexusEvent = { + eventId: '5', + eventTime: '2024-07-11T17:42:53.326959Z', + eventType: 'EVENT_TYPE_NEXUS_OPERATION_SCHEDULED', + taskId: '1098294', + nexusOperationScheduledEventAttributes: { + endpoint: 'sdk_go_nexus_test_ep_c5559a73_33a7_436e_bfa0_f794d2f26795', + service: 'test', + operation: 'custom-op', + input: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + }, + data: 'InN0YXJ0LWFzeW5jIg==', + }, + scheduleToCloseTimeout: '0s', + workflowTaskCompletedEventId: '4', + requestId: '4e393ac4-564f-4856-9ddc-fbe1fdc0a7f7', + endpointId: 'f0a6cc80-cb36-4e3c-92a6-92681687b0ee', + }, + }; + const event = await toEvent(nexusEvent); + expect(getSummaryAttribute(event)).toStrictEqual({ + key: 'input', + value: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + }, + data: 'InN0YXJ0LWFzeW5jIg==', + }, + }); + }); +}); diff --git a/src/lib/utilities/get-single-attribute-for-event.ts b/src/lib/utilities/get-single-attribute-for-event.ts index 09c6f4ead..a2d75e622 100644 --- a/src/lib/utilities/get-single-attribute-for-event.ts +++ b/src/lib/utilities/get-single-attribute-for-event.ts @@ -1,11 +1,29 @@ -import { isEventGroup } from '$lib/models/event-groups'; +import { isEvent } from '$lib/models/event-history'; +import type { Payloads } from '$lib/types'; +import type { + PendingActivity, + PendingNexusOperation, + WorkflowEvent, +} from '$lib/types/events'; +import type { Payload } from '$lib/types/events'; import { capitalize } from '$lib/utilities/format-camel-case'; + +import { decodePayload, isSinglePayload } from './decode-payload'; import type { CombinedAttributes } from './format-event-attributes'; -import { isLocalActivityMarkerEvent } from './is-event-type'; +import { has } from './has'; +import { isObject } from './is'; +import { + isLocalActivityMarkerEvent, + isWorkflowExecutionUpdateAcceptedEvent, +} from './is-event-type'; +import { + isPendingActivity, + isPendingNexusOperation, +} from './is-pending-activity'; -type SummaryAttribute = { +export type SummaryAttribute = { key: string; - value: string | Record; + value: string | Record | Payloads; }; const emptyAttribute: SummaryAttribute = { key: '', value: '' }; @@ -28,15 +46,62 @@ export const shouldDisplayAsPlainText = (key: string): boolean => { return keysForPlainText.has(key); }; +const keysToOmitIfNoValue: Readonly> = new Set([ + 'suggestContinueAsNew', + 'historySizeBytes', +]); + export const shouldDisplayAttribute = ( key: string, value: unknown, -): boolean => { +): key is string => { if (value === null) return false; if (value === undefined) return false; if (value === '') return false; if (value === '0s') return false; if (key === 'type') return false; + if ((!value || value === '0') && keysToOmitIfNoValue.has(key)) return false; + + return true; +}; + +export const pendingActivityKeys = [ + 'attempt', + 'maximumAttempts', + 'heartbeatDetails', + 'lastHeartbeatTime', + 'lastFailure', + 'lastStartedTime', + 'scheduledTime', + 'expirationTime', + 'lastWorkerIdentity', +]; + +export const shouldDisplayPendingAttribute = (key: string): key is string => { + return pendingActivityKeys.includes(key); +}; + +export const shouldDisplayGroupAttribute = ( + key: string, + value: unknown, +): key is string => { + if (value === null) return false; + if (value === undefined) return false; + if (value === '') return false; + if (value === '0s') return false; + if (key === 'type') return false; + if (key === 'workflowId') return false; + if (key === 'initiatedEventId') return false; + if (key === 'startedEventId') return false; + if (key === 'scheduledEventId') return false; + if (key === 'activityId') return false; + if (key === 'namespace') return false; + if (key === 'namespaceId') return false; + if (key === 'workflowTaskCompletedEventId') return false; + if (key === 'taskQueueKind') return false; + + if ((!value || value === '0') && keysToOmitIfNoValue.has(key)) return false; + return true; }; @@ -53,7 +118,21 @@ export const getCodeBlockValue: Parameters[0] = ( value: string | Record, ) => { if (typeof value === 'string') return value; - return value?.payloads ?? value?.indexedFields ?? value?.points ?? value; + + return [value?.payloads, value?.indexedFields, value?.points, value].find( + (v) => v !== undefined, + ); +}; + +export const getStackTrace = (value: unknown) => { + if (!isObject(value)) return undefined; + if (has(value, 'stackTrace') && value.stackTrace) return value.stackTrace; + + for (const key in value) { + if (isObject(value[key])) { + return getStackTrace(value[key]); + } + } }; const keysWithExecutionLinks = [ @@ -65,10 +144,28 @@ const keysWithExecutionLinks = [ 'originalExecutionRunId', ] as const; +export type EventLinkType = + | 'execution' + | 'task-queue' + | 'child-workflow' + | 'nexus-endpoint' + | 'none'; + +export const displayLinkType = ( + key: string, + attributes: CombinedAttributes, +): EventLinkType => { + if (shouldDisplayAsExecutionLink(key)) return 'execution'; + if (shouldDisplayAsTaskQueueLink(key)) return 'task-queue'; + if (shouldDisplayChildWorkflowLink(key, attributes)) return 'child-workflow'; + if (shouldDisplayNexusEndpointLink(key)) return 'nexus-endpoint'; + return 'none'; +}; + // For linking to same workflow but different execution export const shouldDisplayAsExecutionLink = ( key: string, -): key is typeof keysWithExecutionLinks[number] => { +): key is (typeof keysWithExecutionLinks)[number] => { for (const workflowKey of keysWithExecutionLinks) { if (key === workflowKey) return true; } @@ -80,7 +177,7 @@ const keysWithTaskQueueLinks = ['taskQueueName'] as const; export const shouldDisplayAsTaskQueueLink = ( key: string, -): key is typeof keysWithTaskQueueLinks[number] => { +): key is (typeof keysWithTaskQueueLinks)[number] => { for (const taskQueueKey of keysWithTaskQueueLinks) { if (key === taskQueueKey) return true; } @@ -97,7 +194,7 @@ const keysWithChildExecutionLinks = [ export const shouldDisplayChildWorkflowLink = ( key: string, attributes: CombinedAttributes, -): key is typeof keysWithChildExecutionLinks[number] => { +): key is (typeof keysWithChildExecutionLinks)[number] => { const workflowLinkAttributesExist = Boolean( attributes?.workflowExecutionWorkflowId && attributes?.workflowExecutionRunId, @@ -109,9 +206,35 @@ export const shouldDisplayChildWorkflowLink = ( return false; }; -const formatSummaryValue = (key: string, value: unknown): SummaryAttribute => { +const keysWithNexusEndpointLinks = ['endpointId'] as const; + +export const shouldDisplayNexusEndpointLink = (key: string): boolean => { + for (const endpointKey of keysWithNexusEndpointLinks) { + if (key === endpointKey) return true; + } + + return false; +}; + +export const shouldDisplayAsTime = (key: string): boolean => { + return key?.toLowerCase()?.endsWith('time'); +}; + +export const formatSummaryValue = ( + key: string, + value: unknown, +): SummaryAttribute => { if (typeof value === 'object') { + if (isSinglePayload(value)) { + return { key, value }; + } const [firstKey] = Object.keys(value); + if (!firstKey) { + return { key, value: {} }; + } + if (firstKey === 'payloads') { + return { key, value }; + } return { key: key + capitalize(firstKey), value: value[firstKey] }; } else { return { key, value: value.toString() }; @@ -122,13 +245,21 @@ const formatSummaryValue = (key: string, value: unknown): SummaryAttribute => { * A list of the keys that should be shown in the summary view. */ const preferredSummaryKeys = [ + 'activityType', + 'signalName', + 'workflowType', + 'result', 'failure', 'input', - 'activityType', - 'parentInitiatedEventId', + 'outcome', 'workflowExecution', - 'workflowType', 'taskQueue', + 'startToFireTimeout', + 'attempt', + 'historySizeBytes', + 'identity', + 'parentInitiatedEventId', + 'endpointId', ] as const; /** @@ -144,47 +275,111 @@ const getFirstDisplayAttribute = ({ } }; +export const getActivityType = (payload: Payload) => { + if (has(payload, 'ActivityType')) return payload.ActivityType; + if (has(payload, 'activity_type')) return payload.activity_type; +}; + +const isJavaSDK = (event: WorkflowEvent): boolean => { + return !!event.markerRecordedEventAttributes?.details?.type?.payloads; +}; + /** * Iterates through the keys of an event and compares it with the list of * preferred keys. If a preferred key is found, it will be returned. * Otherwise, it will return the first eligible event attribute. */ -const getSummaryAttribute = (event: WorkflowEvent): SummaryAttribute => { + +export const getEventSummaryAttribute = ( + event: WorkflowEvent, +): SummaryAttribute => { const first = getFirstDisplayAttribute(event); - if (isLocalActivityMarkerEvent(event as MarkerRecordedEvent)) { - const payload: any = - event.markerRecordedEventAttributes?.details?.data?.payloads?.[0]; - const activityType = payload?.ActivityType ?? payload?.activity_type; + if (isLocalActivityMarkerEvent(event)) { + const payloads = (event.markerRecordedEventAttributes?.details?.data + ?.payloads || + event.markerRecordedEventAttributes?.details?.type?.payloads || + []) as unknown as Payload[]; + const decodedPayloads = payloads.map((p) => decodePayload(p)); + const payload = decodedPayloads?.[0]; + if (isJavaSDK(event) && payload) { + return formatSummaryValue('ActivityType', payload); + } + const activityType = getActivityType(payload); if (activityType) { return formatSummaryValue('ActivityType', activityType); } } - for (const [key, value] of Object.entries(event.attributes)) { - for (const preferredKey of preferredSummaryKeys) { - if (key === preferredKey && shouldDisplayAttribute(key, value)) + if (isWorkflowExecutionUpdateAcceptedEvent(event)) { + if (event.attributes?.acceptedRequest?.input?.name) { + return { + key: 'name', + value: event.attributes.acceptedRequest.input.name, + }; + } + } + + for (const preferredKey of preferredSummaryKeys) { + for (const [key, value] of Object.entries(event.attributes)) { + if (key === preferredKey && shouldDisplayAttribute(key, value)) { return formatSummaryValue(key, value); + } } } - return first; + return first || emptyAttribute; +}; + +export const getPendingActivitySummaryAttribute = ( + event: PendingActivity, +): SummaryAttribute => { + return { key: 'attempt', value: event.attempt.toString() }; +}; + +export const getPendingNexusOperationSummaryAttribute = ( + event: PendingNexusOperation, +): SummaryAttribute => { + if (!event.attempt) return emptyAttribute; + return { key: 'attempt', value: event.attempt.toString() }; }; -export const getSummaryForEventGroup = ({ - lastEvent, -}: EventGroup): SummaryAttribute => { - return getSummaryAttribute(lastEvent); +export const getSummaryAttribute = ( + event: WorkflowEvent | PendingActivity | PendingNexusOperation, +): SummaryAttribute => { + if (isEvent(event)) return getEventSummaryAttribute(event); + if (isPendingActivity(event)) + return getPendingActivitySummaryAttribute(event); + if (isPendingNexusOperation(event)) + return getPendingNexusOperationSummaryAttribute(event); + return emptyAttribute; }; -export const getSingleAttributeForEvent = ( - event: WorkflowEvent | EventGroup, +export const getPrimaryAttributeForEvent = ( + event: WorkflowEvent, ): SummaryAttribute => { if (!event) return emptyAttribute; - if (isEventGroup(event)) { - return getSummaryForEventGroup(event); + return getSummaryAttribute(event); +}; + +export const getSecondaryAttributeForEvent = ( + event: WorkflowEvent, + primaryKey: string, +): SummaryAttribute => { + if (!event || !event?.attributes) return emptyAttribute; + + for (const preferredKey of preferredSummaryKeys) { + for (const [key, value] of Object.entries(event.attributes)) { + if ( + key === preferredKey && + key !== primaryKey && + shouldDisplayAttribute(key, value) + ) { + return formatSummaryValue(key, value); + } + } } - return getSummaryAttribute(event); + return emptyAttribute; }; diff --git a/src/lib/utilities/get-started-completed-and-task-failed-events.test.ts b/src/lib/utilities/get-started-completed-and-task-failed-events.test.ts index 619cde193..d67e1b048 100644 --- a/src/lib/utilities/get-started-completed-and-task-failed-events.test.ts +++ b/src/lib/utilities/get-started-completed-and-task-failed-events.test.ts @@ -1,8 +1,10 @@ import { describe, expect, it } from 'vitest'; + import { getWorkflowStartedCompletedAndTaskFailedEvents } from './get-started-completed-and-task-failed-events'; -import completedEventHistory from '$fixtures/events.completed.json'; import canceledEventHistory from '$fixtures/events.canceled.json'; +import completedEventHistory from '$fixtures/events.completed.json'; +import continuedAsNewEventHistory from '$fixtures/events.continued-as-new.json'; import failedEventHistory from '$fixtures/events.failed.json'; import runningEventHistory from '$fixtures/events.running.json'; import terminatedEventHistory from '$fixtures/events.terminated.json'; @@ -113,7 +115,7 @@ describe('getWorkflowStartedCompletedAndTaskFailedEvents', () => { expect( getWorkflowStartedCompletedAndTaskFailedEvents([workflowStartedEvent]) .input, - ).toBe('null'); + ).toBe('{"payloads":null}'); }); it('should get the correct input for a completed event history', () => { @@ -121,7 +123,7 @@ describe('getWorkflowStartedCompletedAndTaskFailedEvents', () => { completedEventHistory, ); expect(input).toMatchInlineSnapshot( - '"[\\"1656707328774263000\\",\\"canary\\"]"', + '"{"payloads":["1656707328774263000","canary"]}"', ); }); @@ -135,35 +137,39 @@ describe('getWorkflowStartedCompletedAndTaskFailedEvents', () => { it('should get the correct input for a cancelled event history', () => { const { input } = getWorkflowStartedCompletedAndTaskFailedEvents(canceledEventHistory); - expect(input).toMatchInlineSnapshot('"[1656706850149404400,480000000000]"'); + expect(input).toMatchInlineSnapshot( + '"{"payloads":[1656706850149404400,480000000000]}"', + ); }); it('should get the correct result for a cancelled event history', () => { const { results } = getWorkflowStartedCompletedAndTaskFailedEvents(canceledEventHistory); expect(results).toMatchInlineSnapshot( - '"{\\"type\\":\\"workflowExecutionCanceledEventAttributes\\",\\"workflowTaskCompletedEventId\\":\\"11\\",\\"details\\":null}"', + '"{"type":"workflowExecutionCanceledEventAttributes","workflowTaskCompletedEventId":"11","details":null}"', ); }); it('should get the correct input for a failed event history', () => { const { input } = getWorkflowStartedCompletedAndTaskFailedEvents(failedEventHistory); - expect(input).toMatchInlineSnapshot('"[1656706968987842000]"'); + expect(input).toMatchInlineSnapshot('"{"payloads":[1656706968987842000]}"'); }); it('should get the correct result for a failed event history', () => { const { results } = getWorkflowStartedCompletedAndTaskFailedEvents(failedEventHistory); expect(results).toMatchInlineSnapshot( - '"{\\"type\\":\\"workflowExecutionFailedEventAttributes\\",\\"failure\\":{\\"message\\":\\"failing on attempt 2\\",\\"source\\":\\"GoSDK\\",\\"stackTrace\\":\\"\\",\\"cause\\":null,\\"applicationFailureInfo\\":{\\"type\\":\\"\\",\\"nonRetryable\\":false,\\"details\\":null}},\\"retryState\\":\\"InProgress\\",\\"workflowTaskCompletedEventId\\":\\"4\\",\\"newExecutionRunId\\":\\"15e13ed4-880a-4557-96c6-0116e3d07b8d\\"}"', + '"{"type":"workflowExecutionFailedEventAttributes","failure":{"message":"failing on attempt 2","source":"GoSDK","stackTrace":"","cause":null,"applicationFailureInfo":{"type":"","nonRetryable":false,"details":null}},"retryState":"InProgress","workflowTaskCompletedEventId":"4","newExecutionRunId":"15e13ed4-880a-4557-96c6-0116e3d07b8d"}"', ); }); it('should get the correct input for a running event history', () => { const { input } = getWorkflowStartedCompletedAndTaskFailedEvents(runningEventHistory); - expect(input).toMatchInlineSnapshot('"[1656707029044596700,\\"canary\\"]"'); + expect(input).toMatchInlineSnapshot( + '"{"payloads":[1656707029044596700,"canary"]}"', + ); }); it('should get the correct result for a running event history', () => { @@ -177,7 +183,7 @@ describe('getWorkflowStartedCompletedAndTaskFailedEvents', () => { terminatedEventHistory, ); expect(input).toMatchInlineSnapshot( - '"[1656706488881881600,\\"temporal.fixture.terminated.workflow.id\\",\\"3cbbf515-36da-43b9-a1f3-18a7ec031ddd\\",\\"canary\\"]"', + '"{"payloads":[1656706488881881600,"temporal.fixture.terminated.workflow.id","3cbbf515-36da-43b9-a1f3-18a7ec031ddd","canary"]}"', ); }); @@ -186,22 +192,51 @@ describe('getWorkflowStartedCompletedAndTaskFailedEvents', () => { terminatedEventHistory, ); expect(results).toMatchInlineSnapshot( - '"{\\"type\\":\\"workflowExecutionTerminatedEventAttributes\\",\\"reason\\":\\"reset canary\\",\\"details\\":null,\\"identity\\":\\"history-service\\"}"', + '"{"type":"workflowExecutionTerminatedEventAttributes","reason":"reset canary","details":null,"identity":"history-service"}"', ); }); it('should get the correct input for a timedOut event history', () => { const { input } = getWorkflowStartedCompletedAndTaskFailedEvents(timedOutEventHistory); - expect(input).toMatchInlineSnapshot('"[1656683778738403300,\\"canary\\"]"'); + expect(input).toMatchInlineSnapshot( + '"{"payloads":[1656683778738403300,"canary"]}"', + ); }); it('should get the correct result for a timedOut event history', () => { const { results } = getWorkflowStartedCompletedAndTaskFailedEvents(timedOutEventHistory); expect(results).toMatchInlineSnapshot( - '"{\\"type\\":\\"workflowExecutionTimedOutEventAttributes\\",\\"retryState\\":\\"RetryPolicyNotSet\\",\\"newExecutionRunId\\":\\"\\"}"', + '"{"type":"workflowExecutionTimedOutEventAttributes","retryState":"RetryPolicyNotSet","newExecutionRunId":""}"', + ); + }); + + it('should set contAsNew to false for a non continuedAsNew event history', () => { + const { contAsNew } = + getWorkflowStartedCompletedAndTaskFailedEvents(timedOutEventHistory); + expect(contAsNew).toBe(false); + }); + + it('should get the correct input for a continuedAsNew event history', () => { + const { input } = getWorkflowStartedCompletedAndTaskFailedEvents( + continuedAsNewEventHistory, + ); + expect(input).toMatchInlineSnapshot('"{"payloads":[3,2]}"'); + }); + + it('should get the correct result for a continuedAsNew event history', () => { + const { results } = getWorkflowStartedCompletedAndTaskFailedEvents( + continuedAsNewEventHistory, ); + expect(results).toMatchInlineSnapshot('"{"payloads":[4,1]}"'); + }); + + it('should set contAsNew to true for a continuedAsNew event history', () => { + const { contAsNew } = getWorkflowStartedCompletedAndTaskFailedEvents( + continuedAsNewEventHistory, + ); + expect(contAsNew).toBe(true); }); it('should work as expected with a WorkflowCompletedEvent with a null result', () => { @@ -251,167 +286,6 @@ describe('getWorkflowStartedCompletedAndTaskFailedEvents', () => { ]; const { results } = getWorkflowStartedCompletedAndTaskFailedEvents(history); - expect(results).toBe('"result"'); - }); - - it('should get the correct error for a completed event history without any failed tasks', () => { - const { error } = getWorkflowStartedCompletedAndTaskFailedEvents( - completedEventHistory, - ); - expect(error).toMatchInlineSnapshot('undefined'); - }); - - it('should get the correct error for a WorkflowTaskFailed event', () => { - const failedEvent = { - eventId: '15', - eventTime: '2022-10-31T22:41:28.917920758Z', - eventType: 'WorkflowTaskFailed', - version: '0', - taskId: '56713476', - workflowTaskFailedEventAttributes: { - cause: 'NonDeterministicError', - failure: { - cause: null, - }, - }, - attributes: { - type: 'workflowTaskFailedEventAttributes', - cause: 'NonDeterministicError', - failure: { - cause: null, - }, - }, - classification: 'Failed', - category: 'workflow', - id: '11', - name: 'WorkflowTaskFailed', - timestamp: '2022-10-31 UTC 22:41:28.91', - }; - const history = [failedEvent, ...runningEventHistory]; - - const { error } = getWorkflowStartedCompletedAndTaskFailedEvents(history); - expect(error).toBe(failedEvent); - }); - - it('should get the correct error for a workflow that has a completed task after a failed task in descending order', () => { - const history = [ - { - eventId: '12', - eventTime: '2022-07-01T20:28:52.916365546Z', - eventType: 'WorkflowTaskCompleted', - version: '0', - taskId: '29887669', - workflowTaskCompletedEventAttributes: { - scheduledEventId: '15', - startedEventId: '16', - identity: '83579@MacBook-Pro-2.local@', - binaryChecksum: 'e56c0141e58df0bd405138565d0526f9', - }, - attributes: { - type: 'workflowTaskCompletedEventAttributes', - scheduledEventId: '15', - startedEventId: '16', - identity: '83579@MacBook-Pro-2.local@', - binaryChecksum: 'e56c0141e58df0bd405138565d0526f9', - }, - classification: 'Completed', - category: 'workflow', - id: '12', - name: 'WorkflowTaskCompleted', - timestamp: '2022-07-01 UTC 20:28:52.91', - }, - { - eventId: '11', - eventTime: '2022-10-31T22:41:28.917920758Z', - eventType: 'WorkflowTaskFailed', - version: '0', - taskId: '56713476', - workflowTaskFailedEventAttributes: { - cause: 'NonDeterministicError', - failure: { - cause: null, - }, - }, - attributes: { - type: 'workflowTaskFailedEventAttributes', - cause: 'NonDeterministicError', - failure: { - cause: null, - }, - }, - classification: 'Failed', - category: 'workflow', - id: '11', - name: 'WorkflowTaskFailed', - timestamp: '2022-10-31 UTC 22:41:28.91', - }, - ]; - - const { error } = getWorkflowStartedCompletedAndTaskFailedEvents( - history, - 'descending', - ); - expect(error).toMatchInlineSnapshot('undefined'); - }); - - it('should get the correct error for a workflow that has a completed task after a failed task is ascending order', () => { - const history = [ - { - eventId: '11', - eventTime: '2022-10-31T22:41:28.917920758Z', - eventType: 'WorkflowTaskFailed', - version: '0', - taskId: '56713476', - workflowTaskFailedEventAttributes: { - cause: 'NonDeterministicError', - failure: { - cause: null, - }, - }, - attributes: { - type: 'workflowTaskFailedEventAttributes', - cause: 'NonDeterministicError', - failure: { - cause: null, - }, - }, - classification: 'Failed', - category: 'workflow', - id: '11', - name: 'WorkflowTaskFailed', - timestamp: '2022-10-31 UTC 22:41:28.91', - }, - { - eventId: '12', - eventTime: '2022-07-01T20:28:52.916365546Z', - eventType: 'WorkflowTaskCompleted', - version: '0', - taskId: '29887669', - workflowTaskCompletedEventAttributes: { - scheduledEventId: '15', - startedEventId: '16', - identity: '83579@MacBook-Pro-2.local@', - binaryChecksum: 'e56c0141e58df0bd405138565d0526f9', - }, - attributes: { - type: 'workflowTaskCompletedEventAttributes', - scheduledEventId: '15', - startedEventId: '16', - identity: '83579@MacBook-Pro-2.local@', - binaryChecksum: 'e56c0141e58df0bd405138565d0526f9', - }, - classification: 'Completed', - category: 'workflow', - id: '12', - name: 'WorkflowTaskCompleted', - timestamp: '2022-07-01 UTC 20:28:52.91', - }, - ]; - - const { error } = getWorkflowStartedCompletedAndTaskFailedEvents( - history, - 'ascending', - ); - expect(error).toMatchInlineSnapshot('undefined'); + expect(results).toBe('{"payloads":"result"}'); }); }); diff --git a/src/lib/utilities/get-started-completed-and-task-failed-events.ts b/src/lib/utilities/get-started-completed-and-task-failed-events.ts index 0824f0d4c..3a0e94598 100644 --- a/src/lib/utilities/get-started-completed-and-task-failed-events.ts +++ b/src/lib/utilities/get-started-completed-and-task-failed-events.ts @@ -1,11 +1,24 @@ -import { isWorkflowExecutionCompletedEvent } from './is-event-type'; +import type { + WorkflowEvent, + WorkflowExecutionCanceledEvent, + WorkflowExecutionCompletedEvent, + WorkflowExecutionContinuedAsNewEvent, + WorkflowExecutionFailedEvent, + WorkflowExecutionStartedEvent, + WorkflowExecutionTerminatedEvent, + WorkflowExecutionTimedOutEvent, +} from '$lib/types/events'; + +import { + isWorkflowExecutionCompletedEvent, + isWorkflowExecutionContinuedAsNewEvent, +} from './is-event-type'; import { stringifyWithBigInt } from './parse-with-big-int'; -import type { EventSortOrder } from '$lib/stores/event-view'; -type WorkflowInputAndResults = { +export type WorkflowInputAndResults = { input: string; results: string; - error: WorkflowTaskFailedEvent; + contAsNew: boolean; }; type CompletionEvent = @@ -38,81 +51,54 @@ const isCompletionEvent = (event: WorkflowEvent): event is CompletionEvent => { return false; }; -const isFailedTaskEvent = ( - event: WorkflowEvent, -): event is WorkflowTaskFailedEvent => { - return event.eventType === 'WorkflowTaskFailed'; -}; - -const isCompletedTaskEvent = ( - event: WorkflowEvent, -): event is WorkflowTaskCompletedEvent => { - return event.eventType === 'WorkflowTaskCompleted'; -}; - const getEventResult = (event: CompletionEvent) => { + if (isWorkflowExecutionContinuedAsNewEvent(event)) { + return event.attributes.input; + } + if (isWorkflowExecutionCompletedEvent(event)) { if (event.attributes.result === null) return null; - return event.attributes.result.payloads; + return event.attributes.result; } return event.attributes; }; export const getWorkflowStartedCompletedAndTaskFailedEvents = ( - events: WorkflowEvents, - eventSortOrder: EventSortOrder = 'descending', + eventHistory: WorkflowEvent[], ): WorkflowInputAndResults => { let input: string; let results: string; - let error: WorkflowTaskFailedEvent; + let contAsNew = false; let workflowStartedEvent: WorkflowExecutionStartedEvent; let workflowCompletedEvent: CompletionEvent; - let workflowTaskFailedEvent: WorkflowTaskFailedEvent; - let hasCompletedTaskEvent = false; - for (const event of events) { + for (const event of eventHistory) { if (isStartedEvent(event)) { workflowStartedEvent = event; continue; } else if (isCompletionEvent(event)) { workflowCompletedEvent = event; continue; - } else if (isCompletedTaskEvent(event)) { - // If there is a completed workflow task after a failed task - if (eventSortOrder === 'descending') { - // then we don't need to look for a failed task - hasCompletedTaskEvent = true; - } else if (eventSortOrder === 'ascending' && workflowTaskFailedEvent) { - // or we need to reset the failed event - workflowTaskFailedEvent = undefined; - } - continue; - } else if (!hasCompletedTaskEvent && isFailedTaskEvent(event)) { - workflowTaskFailedEvent = event; - continue; } } if (workflowStartedEvent) { input = stringifyWithBigInt( - workflowStartedEvent?.workflowExecutionStartedEventAttributes?.input - ?.payloads ?? null, + workflowStartedEvent?.workflowExecutionStartedEventAttributes?.input ?? + null, ); } if (workflowCompletedEvent) { + contAsNew = isWorkflowExecutionContinuedAsNewEvent(workflowCompletedEvent); results = stringifyWithBigInt(getEventResult(workflowCompletedEvent)); } - if (workflowTaskFailedEvent) { - error = workflowTaskFailedEvent; - } - return { input, results, - error, + contAsNew, }; }; diff --git a/src/lib/utilities/get-truncated-word.test.ts b/src/lib/utilities/get-truncated-word.test.ts index c3b09ceae..4ad8d3e6d 100644 --- a/src/lib/utilities/get-truncated-word.test.ts +++ b/src/lib/utilities/get-truncated-word.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { getTruncatedWord } from './get-truncated-word'; describe('getTruncatedWord', () => { diff --git a/src/lib/utilities/get-workflow-relationships.test.ts b/src/lib/utilities/get-workflow-relationships.test.ts new file mode 100644 index 000000000..80c289aa2 --- /dev/null +++ b/src/lib/utilities/get-workflow-relationships.test.ts @@ -0,0 +1,244 @@ +import { describe, expect, it } from 'vitest'; + +import { toWorkflowExecution } from '$lib/models/workflow-execution'; + +import { decodePayload } from './decode-payload'; +import { getWorkflowRelationships } from './get-workflow-relationships'; + +import childEvents from '$fixtures/events.children.json'; +import completedEvents from '$fixtures/events.completed.json'; +import continuedAsNewEvents from '$fixtures/events.continued-as-new.json'; +import failedEvents from '$fixtures/events.failed.json'; +import timedOutEvents from '$fixtures/events.timed-out.json'; +import namespaces from '$fixtures/namespaces.json'; +import completedWorkflow from '$fixtures/workflow.completed.json'; +import continuedAsNewWorkflow from '$fixtures/workflow.continued-as-new.json'; +import failedWorkflow from '$fixtures/workflow.failed.json'; +import pendingChildrenWorkflow from '$fixtures/workflow.pending-children.json'; +import runningWorkflow from '$fixtures/workflow.running.json'; +import scheduledWorkflow from '$fixtures/workflow.scheduled.json'; +import timedOutWorkflow from '$fixtures/workflow.timed-out.json'; + +describe('getWorkflowRelationships', () => { + it('hasChildren should return true if there are pending children', () => { + expect( + getWorkflowRelationships( + toWorkflowExecution(pendingChildrenWorkflow), + completedEvents, + namespaces.namespaces, + ).hasChildren, + ).toBe(true); + }); + + it('hasChildren should return true if there are pending children and non-pending children', () => { + expect( + getWorkflowRelationships( + toWorkflowExecution(pendingChildrenWorkflow), + childEvents, + namespaces.namespaces, + ).hasChildren, + ).toBe(true); + expect( + getWorkflowRelationships( + toWorkflowExecution(pendingChildrenWorkflow), + childEvents, + namespaces.namespaces, + ).children.length, + ).toBe(15); + }); + + it('parentNamespaceName should return undefined if parentNamespaceId does not match namespaces list', () => { + expect( + getWorkflowRelationships( + toWorkflowExecution(pendingChildrenWorkflow), + childEvents, + namespaces.namespaces, + ).parentNamespaceName, + ).toBe(undefined); + }); + + it('hasChildren should return true if there are no pending children and non-pending children', () => { + expect( + getWorkflowRelationships( + toWorkflowExecution(runningWorkflow), + childEvents, + namespaces.namespaces, + ).hasChildren, + ).toBe(true); + expect( + getWorkflowRelationships( + toWorkflowExecution(runningWorkflow), + childEvents, + namespaces.namespaces, + ).children.length, + ).toBe(15); + }); + + it('hasRelationships should return false if there are is not a parent, pending children, first, previous, or next', () => { + expect( + getWorkflowRelationships( + toWorkflowExecution(runningWorkflow), + completedEvents, + namespaces.namespaces, + ).hasChildren, + ).toBe(false); + }); + + it('parentNamespaceName should return namespace name if parentNamespaceId does match namespaces list', () => { + expect( + getWorkflowRelationships( + toWorkflowExecution(runningWorkflow), + completedEvents, + namespaces.namespaces, + ).parentNamespaceName, + ).toBe('canary'); + }); + + it('should return the firstExecutionRunId for first on a workflowExecutionStartedEvent', () => { + const workflowExecutionStartedEvent = continuedAsNewEvents.find( + (event) => event?.name === 'WorkflowExecutionStarted', + ); + const firstExecutionRunId = + workflowExecutionStartedEvent?.attributes?.firstExecutionRunId; + + expect( + getWorkflowRelationships( + toWorkflowExecution(continuedAsNewWorkflow), + continuedAsNewEvents, + namespaces.namespaces, + ).first, + ).toBe(firstExecutionRunId); + }); + + it('should not return the firstExecutionRunId for first on a workflowExecutionStartedEvent if the id matches the runId', () => { + const workflowExecutionStartedEvent = continuedAsNewEvents.find( + (event) => event?.name === 'WorkflowExecutionStarted', + ); + const firstExecutionRunId = + workflowExecutionStartedEvent?.attributes?.firstExecutionRunId; + const continuedAsNewWorkflowCopy = JSON.parse( + JSON.stringify(continuedAsNewWorkflow), + ); + expect( + getWorkflowRelationships( + toWorkflowExecution(continuedAsNewWorkflowCopy), + continuedAsNewEvents, + namespaces.namespaces, + ).first, + ).toBe(firstExecutionRunId); + + continuedAsNewWorkflowCopy.workflowExecutionInfo.execution.runId = + firstExecutionRunId; + + expect( + getWorkflowRelationships( + toWorkflowExecution(continuedAsNewWorkflowCopy), + continuedAsNewEvents, + namespaces.namespaces, + ).first, + ).toBe(undefined); + }); + + it('should return the continuedExecutionRunId for previous on a WorkflowExecutionStarted event', () => { + const workflowExecutionStartedEvent = continuedAsNewEvents.find( + (event) => event?.name === 'WorkflowExecutionStarted', + ); + const continuedExecutionRunId = + workflowExecutionStartedEvent?.attributes?.continuedExecutionRunId; + expect( + getWorkflowRelationships( + toWorkflowExecution(continuedAsNewWorkflow), + continuedAsNewEvents, + namespaces.namespaces, + ).previous, + ).toBe(continuedExecutionRunId); + }); + + it('should return the newExecutionRunId for next on a WorkflowExecutionContinuedAsNew event', () => { + const workflowExecutionContinuedAsNewEvent = continuedAsNewEvents.find( + (event) => event?.name === 'WorkflowExecutionContinuedAsNew', + ); + const newExecutionRunId = + workflowExecutionContinuedAsNewEvent?.attributes?.newExecutionRunId; + + expect( + getWorkflowRelationships( + toWorkflowExecution(continuedAsNewWorkflow), + continuedAsNewEvents, + namespaces.namespaces, + ).next, + ).toBe(newExecutionRunId); + }); + + it('should return the newExecutionRunId for next on a WorkflowExecutionCompleted event', () => { + const workflowExecutionCompletedEvent = completedEvents.find( + (event) => event?.name === 'WorkflowExecutionCompleted', + ); + const newExecutionRunId = + workflowExecutionCompletedEvent?.attributes?.newExecutionRunId; + + expect( + getWorkflowRelationships( + toWorkflowExecution(completedWorkflow), + completedEvents, + namespaces.namespaces, + ).next, + ).toBe(newExecutionRunId); + }); + + it('should return the newExecutionRunId for next on a WorkflowExecutionTimedOut event', () => { + const workflowExecutionCompletedEvent = timedOutEvents.find( + (event) => event?.name === 'WorkflowExecutionTimedOut', + ); + const newExecutionRunId = + workflowExecutionCompletedEvent?.attributes?.newExecutionRunId; + + expect( + getWorkflowRelationships( + toWorkflowExecution(timedOutWorkflow), + timedOutEvents, + namespaces.namespaces, + ).next, + ).toBe(newExecutionRunId); + }); + + it('should return the newExecutionRunId for next on a WorkflowExecutionFailed event', () => { + const workflowExecutionCompletedEvent = failedEvents.find( + (event) => event?.name === 'WorkflowExecutionFailed', + ); + const newExecutionRunId = + workflowExecutionCompletedEvent?.attributes?.newExecutionRunId; + + expect( + getWorkflowRelationships( + toWorkflowExecution(failedWorkflow), + failedEvents, + namespaces.namespaces, + ).next, + ).toBe(newExecutionRunId); + }); + + it('should return the decoded scheduleID for a scheduled workflows', () => { + const workflowScheduledId = decodePayload( + scheduledWorkflow?.workflowExecutionInfo?.searchAttributes?.indexedFields + ?.TemporalScheduledById, + ); + expect( + getWorkflowRelationships( + toWorkflowExecution(scheduledWorkflow), + completedEvents, + namespaces.namespaces, + ).scheduleId, + ).toBe(workflowScheduledId); + }); + + it('should return empty string for a non-scheduled workflows', () => { + expect( + getWorkflowRelationships( + toWorkflowExecution(completedWorkflow), + completedEvents, + namespaces.namespaces, + ).scheduleId, + ).toBe(''); + }); +}); diff --git a/src/lib/utilities/get-workflow-relationships.ts b/src/lib/utilities/get-workflow-relationships.ts new file mode 100644 index 000000000..e8498540d --- /dev/null +++ b/src/lib/utilities/get-workflow-relationships.ts @@ -0,0 +1,136 @@ +import type { DescribeNamespaceResponse } from '$lib/types'; +import type { + ChildWorkflowExecutionCanceledEvent, + ChildWorkflowExecutionCompletedEvent, + ChildWorkflowExecutionFailedEvent, + ChildWorkflowExecutionTerminatedEvent, + ChildWorkflowExecutionTimedOutEvent, + IterableEvent, + WorkflowEvents, +} from '$lib/types/events'; +import type { WorkflowExecution } from '$lib/types/workflows'; +import type { WorkflowIdentifier } from '$lib/types/workflows'; + +import { has } from './has'; +import { isString } from './is'; +import { + isChildWorkflowExecutionCanceledEvent, + isChildWorkflowExecutionCompletedEvent, + isChildWorkflowExecutionFailedEvent, + isChildWorkflowExecutionTerminatedEvent, + isChildWorkflowExecutionTimedOutEvent, + isWorkflowExecutionStartedEvent, +} from './is-event-type'; + +const getNewExecutionId = (events: WorkflowEvents): string | undefined => { + for (const event of events) { + if ( + has(event.attributes, 'newExecutionRunId') && + isString(event.attributes.newExecutionRunId) + ) { + return event.attributes.newExecutionRunId; + } + } +}; + +export type ChildWorkflowClosedEvent = + | ChildWorkflowExecutionCompletedEvent + | ChildWorkflowExecutionFailedEvent + | ChildWorkflowExecutionCanceledEvent + | ChildWorkflowExecutionTimedOutEvent + | ChildWorkflowExecutionTerminatedEvent; + +export const isChildWorkflowClosedEvent = ( + event: IterableEvent, +): event is ChildWorkflowClosedEvent => { + return ( + isChildWorkflowExecutionCompletedEvent(event) || + isChildWorkflowExecutionFailedEvent(event) || + isChildWorkflowExecutionCanceledEvent(event) || + isChildWorkflowExecutionTimedOutEvent(event) || + isChildWorkflowExecutionTerminatedEvent(event) + ); +}; + +type WorkflowRelationships = { + hasRelationships: boolean; + hasChildren: boolean; + children: ChildWorkflowClosedEvent[]; + first: string | undefined; + previous: string | undefined; + parent: WorkflowIdentifier | undefined; + parentNamespaceName: string | undefined; + next: string | undefined; + scheduleId: string | undefined; + relationshipCount: number; +}; + +export const getWorkflowRelationships = ( + workflow: WorkflowExecution | null, + fullEventHistory: WorkflowEvents, + namespaces: DescribeNamespaceResponse[], +): WorkflowRelationships => { + const children = fullEventHistory.filter((event) => + isChildWorkflowClosedEvent(event), + ) as ChildWorkflowClosedEvent[]; + const hasChildren = !!workflow?.pendingChildren.length || !!children.length; + const parent = workflow?.parent; + + const parentNamespaceName = namespaces?.find((namespace) => { + return namespace.namespaceInfo.id === workflow.parentNamespaceId; + })?.namespaceInfo?.name; + + const workflowExecutionStartedEvent = fullEventHistory.find( + isWorkflowExecutionStartedEvent, + ); + + const newExecutionRunId = getNewExecutionId(fullEventHistory); + + const firstExecutionRunId = + workflowExecutionStartedEvent?.attributes?.firstExecutionRunId; + + const first = + firstExecutionRunId === workflow?.runId ? undefined : firstExecutionRunId; + + const previous = + workflowExecutionStartedEvent?.attributes?.continuedExecutionRunId; + + let scheduleId = ''; + const temporalScheduledById = + workflow?.searchAttributes?.indexedFields?.TemporalScheduledById; + + if (typeof temporalScheduledById === 'string') { + scheduleId = temporalScheduledById; + } + + const hasRelationships = !!( + parent || + hasChildren || + first || + previous || + newExecutionRunId || + scheduleId + ); + + const relationshipCount = + (parent ? 1 : 0) + + workflow?.pendingChildren.length + + children.length + + (first ? 1 : 0) + + (previous ? 1 : 0) + + (newExecutionRunId ? 1 : 0) + + (scheduleId ? 1 : 0); + + return { + hasRelationships, + hasChildren, + children, + first, + previous, + parent, + parentNamespaceName, + next: newExecutionRunId, + scheduleId, + relationshipCount, + }; +}; diff --git a/src/lib/utilities/get-workflow-status-filter-code.test.ts b/src/lib/utilities/get-workflow-status-filter-code.test.ts index 931e994a6..1eea59cab 100644 --- a/src/lib/utilities/get-workflow-status-filter-code.test.ts +++ b/src/lib/utilities/get-workflow-status-filter-code.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { getStatusFilterCode } from './get-workflow-status-filter-code'; describe('getStatusFilterCode', () => { diff --git a/src/lib/utilities/get-workflow-status-filter-code.ts b/src/lib/utilities/get-workflow-status-filter-code.ts index 76d857c8b..fca20b1cb 100644 --- a/src/lib/utilities/get-workflow-status-filter-code.ts +++ b/src/lib/utilities/get-workflow-status-filter-code.ts @@ -1,3 +1,5 @@ +import type { WorkflowStatus } from '$lib/types/workflows'; + type ExecutionStatusCodes = '1' | '2' | '3' | '4' | '5' | '6' | '7'; export const getStatusFilterCode = ( diff --git a/src/lib/utilities/get-workflow-task-failed-event.test.ts b/src/lib/utilities/get-workflow-task-failed-event.test.ts new file mode 100644 index 000000000..d125810c9 --- /dev/null +++ b/src/lib/utilities/get-workflow-task-failed-event.test.ts @@ -0,0 +1,163 @@ +import { describe, expect, it } from 'vitest'; + +import { getWorkflowTaskFailedEvent } from './get-workflow-task-failed-event'; + +import runningEventHistory from '$fixtures/events.running.json'; + +describe('getWorkflowTaskFailedEvent', () => { + const failedEvent = { + eventId: '15', + eventTime: '2022-10-31T22:41:28.917920758Z', + eventType: 'WorkflowTaskFailed', + version: '0', + taskId: '56713476', + workflowTaskFailedEventAttributes: { + cause: 'NonDeterministicError', + failure: { + cause: null, + }, + }, + attributes: { + type: 'workflowTaskFailedEventAttributes', + cause: 'NonDeterministicError', + failure: { + cause: null, + }, + }, + classification: 'Failed', + category: 'workflow', + id: '11', + name: 'WorkflowTaskFailed', + timestamp: '2022-10-31 UTC 22:41:28.91', + }; + + const completedEvent = { + eventId: '16', + eventTime: '2022-07-01T20:20:50.181498878Z', + eventType: 'WorkflowTaskCompleted', + version: '0', + taskId: '29875730', + workflowTaskCompletedEventAttributes: { + scheduledEventId: '9', + startedEventId: '10', + identity: '83579@MacBook-Pro-2.local@', + binaryChecksum: 'e56c0141e58df0bd405138565d0526f9', + }, + }; + + it('should get the correct error for a WorkflowTaskFailed event when events are in descending order', () => { + const history = [failedEvent, ...runningEventHistory]; + const error = getWorkflowTaskFailedEvent(history, 'descending'); + expect(error).toBe(failedEvent); + }); + + it('should get the correct error for a WorkflowTaskFailed event when events are in ascending order', () => { + const history = [failedEvent, ...runningEventHistory].reverse(); + const error = getWorkflowTaskFailedEvent(history, 'ascending'); + expect(error).toBe(failedEvent); + }); + + it('should get the last error for a workflow that has multiple WorkflowTaskFailed events', () => { + const history = [ + { + eventId: '11', + eventTime: '2022-10-31T22:41:28.917920758Z', + eventType: 'WorkflowTaskFailed', + version: '0', + taskId: '56713476', + workflowTaskFailedEventAttributes: { + cause: 'NonDeterministicError', + failure: { + cause: null, + }, + }, + attributes: { + type: 'workflowTaskFailedEventAttributes', + cause: 'NonDeterministicError', + failure: { + cause: null, + }, + }, + classification: 'Failed', + category: 'workflow', + id: '11', + name: 'WorkflowTaskFailed', + timestamp: '2022-10-31 UTC 22:41:28.91', + }, + { + eventId: '10', + eventTime: '2022-10-31T22:41:28.917920758Z', + eventType: 'WorkflowTaskFailed', + version: '0', + taskId: '56713476', + workflowTaskFailedEventAttributes: { + cause: 'NonDeterministicError', + failure: { + cause: null, + }, + }, + attributes: { + type: 'workflowTaskFailedEventAttributes', + cause: 'NonDeterministicError', + failure: { + cause: null, + }, + }, + classification: 'Failed', + category: 'workflow', + id: '10', + name: 'WorkflowTaskFailed', + timestamp: '2022-10-31 UTC 22:41:28.91', + }, + ]; + + let error = getWorkflowTaskFailedEvent(history, 'descending'); + const expectedError = ` + { + "attributes": { + "cause": "NonDeterministicError", + "failure": { + "cause": null, + }, + "type": "workflowTaskFailedEventAttributes", + }, + "category": "workflow", + "classification": "Failed", + "eventId": "11", + "eventTime": "2022-10-31T22:41:28.917920758Z", + "eventType": "WorkflowTaskFailed", + "id": "11", + "name": "WorkflowTaskFailed", + "taskId": "56713476", + "timestamp": "2022-10-31 UTC 22:41:28.91", + "version": "0", + "workflowTaskFailedEventAttributes": { + "cause": "NonDeterministicError", + "failure": { + "cause": null, + }, + }, + } + `; + expect(error).toMatchInlineSnapshot(expectedError); + + error = getWorkflowTaskFailedEvent([...history].reverse(), 'ascending'); + expect(error).toMatchInlineSnapshot(expectedError); + }); + + it('should not return WorkflowTaskFailed event if CompletedWorkflowTask event occurs after WorkflowTaskFailed event when events are in descending order', () => { + const history = [completedEvent, failedEvent, ...runningEventHistory]; + const error = getWorkflowTaskFailedEvent(history, 'descending'); + expect(error).toBe(undefined); + }); + + it('should not return WorkflowTaskFailed event if CompletedWorkflowTask event occurs after WorkflowTaskFailed event when events are in ascending order', () => { + const history = [ + completedEvent, + failedEvent, + ...runningEventHistory, + ].reverse(); + const error = getWorkflowTaskFailedEvent(history, 'ascending'); + expect(error).toBe(undefined); + }); +}); diff --git a/src/lib/utilities/get-workflow-task-failed-event.ts b/src/lib/utilities/get-workflow-task-failed-event.ts new file mode 100644 index 000000000..d20ad1b10 --- /dev/null +++ b/src/lib/utilities/get-workflow-task-failed-event.ts @@ -0,0 +1,85 @@ +import type { EventSortOrder } from '$lib/stores/event-view'; +import type { WorkflowTaskFailedEventAttributes } from '$lib/types'; +import type { + HistoryEvent, + WorkflowEvent, + WorkflowEvents, + WorkflowTaskCompletedEvent, + WorkflowTaskFailedEvent, +} from '$lib/types/events'; +import type { WorkflowTaskFailedCause } from '$lib/types/workflows'; + +import { isPureWorkflowTaskFailedEvent } from './is-event-type'; +import { toWorkflowTaskFailureReadable } from './screaming-enums'; + +const isFailedTaskEvent = ( + event: WorkflowEvent, +): event is WorkflowTaskFailedEvent => { + return event.eventType === 'WorkflowTaskFailed'; +}; + +const isCompletedTaskEvent = ( + event: WorkflowEvent, +): event is WorkflowTaskCompletedEvent => { + return event.eventType === 'WorkflowTaskCompleted'; +}; + +export const isWorkflowTaskFailedEventDueToReset = ( + event: WorkflowEvent | HistoryEvent, +): boolean => + isPureWorkflowTaskFailedEvent(event) && + getErrorCause(event) === 'ResetWorkflow'; + +export const getErrorCause = ( + error: WorkflowTaskFailedEvent, +): WorkflowTaskFailedCause => { + const { + workflowTaskFailedEventAttributes: { failure, cause }, + } = error as WorkflowTaskFailedEvent & { + workflowTaskFailedEventAttributes: WorkflowTaskFailedEventAttributes; + }; + + if (failure?.applicationFailureInfo?.type === 'workflowTaskHeartbeatError') { + return 'WorkflowTaskHeartbeatError'; + } + + return toWorkflowTaskFailureReadable(cause); +}; + +const getFailedWorkflowTask = ( + fullEventHistory: WorkflowEvents, +): WorkflowTaskFailedEvent | undefined => { + const failedWorkflowTaskIndex = fullEventHistory.findIndex(isFailedTaskEvent); + + if (failedWorkflowTaskIndex < 0) return; + + const completedWorkflowTaskIndex = + fullEventHistory.findIndex(isCompletedTaskEvent); + + const failedWorkflowTask = fullEventHistory.find((event) => + isFailedTaskEvent(event), + ) as WorkflowTaskFailedEvent; + + const cause = getErrorCause(failedWorkflowTask); + + if (cause === 'ResetWorkflow') return; + + if (completedWorkflowTaskIndex < 0) return failedWorkflowTask; + + // History is sorted in descending order, so index of failed task should be less than index of completed task + if (failedWorkflowTaskIndex < completedWorkflowTaskIndex) { + return failedWorkflowTask; + } +}; + +export const getWorkflowTaskFailedEvent = ( + fullEventHistory: WorkflowEvents, + sortOrder: EventSortOrder, +): WorkflowTaskFailedEvent | undefined => { + if (sortOrder === 'descending') { + return getFailedWorkflowTask(fullEventHistory); + } else { + const reversedEventHistory = [...fullEventHistory].reverse(); + return getFailedWorkflowTask(reversedEventHistory); + } +}; diff --git a/src/lib/utilities/handle-error.test.ts b/src/lib/utilities/handle-error.test.ts index 43eab158e..6b7689045 100644 --- a/src/lib/utilities/handle-error.test.ts +++ b/src/lib/utilities/handle-error.test.ts @@ -1,4 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + import { handleError } from './handle-error'; import { routeForLoginPage } from './route-for'; @@ -26,8 +27,7 @@ describe('handleError', () => { response: null as unknown as Response, }; - handleError(error); - + expect(() => handleError(error)).toThrowError(); expect(window.location.assign).toHaveBeenCalledWith(routeForLoginPage()); }); @@ -38,8 +38,7 @@ describe('handleError', () => { response: null as unknown as Response, }; - handleError(error); - + expect(() => handleError(error)).toThrowError(); expect(window.location.assign).toHaveBeenCalledWith(routeForLoginPage()); }); @@ -50,8 +49,7 @@ describe('handleError', () => { response: null as unknown as Response, }; - handleError(error); - + expect(() => handleError(error)).toThrowError(); expect(window.location.assign).toHaveBeenCalledWith(routeForLoginPage()); }); @@ -62,8 +60,7 @@ describe('handleError', () => { response: null as unknown as Response, }; - handleError(error); - + expect(() => handleError(error)).toThrowError(); expect(window.location.assign).toHaveBeenCalledWith(routeForLoginPage()); }); @@ -73,49 +70,54 @@ describe('handleError', () => { response: null as unknown as Response, }; - handleError(error); - + expect(() => handleError(error)).toThrowError(); expect(window.location.assign).not.toHaveBeenCalled(); }); - it('should add notification if it is a NetworkError', () => { + it('should add toast if it is a NetworkError', () => { const error = { statusCode: 500, statusText: 'Uh oh', response: 'lol' as unknown as Response, }; - const notifications = { - add: vi.fn(), + const toasts = { + push: vi.fn(), }; const errors = { set: vi.fn(), }; - try { - handleError(error, notifications, errors); - } catch (error) { - expect(notifications.add).toHaveBeenCalledWith('error', '500 Uh oh'); - expect(errors.set).toHaveBeenCalledWith({ ...error }); - } + expect(() => handleError(error, toasts, errors)).toThrowError(); + expect(toasts.push).toHaveBeenCalledWith({ + variant: 'error', + message: '500 Uh oh', + }); + expect(errors.set).toHaveBeenCalledWith({ ...error }); }); - it('should add a notification on a string error', () => { - const notifications = { - add: vi.fn(), + it('should add a toast on a string error', () => { + const toasts = { + push: vi.fn(), }; - handleError('lol', notifications); - expect(notifications.add).toHaveBeenCalledWith('error', 'lol'); + expect(() => handleError(new Error('lol'), toasts)).toThrowError(); + expect(toasts.push).toHaveBeenCalledWith({ + variant: 'error', + message: 'lol', + }); }); - it('should add a notification on an error', () => { - const notifications = { - add: vi.fn(), + it('should add a toast on an error', () => { + const toasts = { + push: vi.fn(), }; - handleError(new Error('lol'), notifications); - expect(notifications.add).toHaveBeenCalledWith('error', 'lol'); + expect(() => handleError(new Error('lol'), toasts)).toThrowError(); + expect(toasts.push).toHaveBeenCalledWith({ + variant: 'error', + message: 'lol', + }); }); }); diff --git a/src/lib/utilities/handle-error.ts b/src/lib/utilities/handle-error.ts index 8370efeec..f108d7ad1 100644 --- a/src/lib/utilities/handle-error.ts +++ b/src/lib/utilities/handle-error.ts @@ -1,46 +1,61 @@ -import { browser } from '$app/env'; +import { BROWSER } from 'esm-env'; + import { networkError } from '$lib/stores/error'; -import { notifications as notificationStore } from '$lib/stores/notifications'; +import { toaster } from '$lib/stores/toaster'; +import type { NetworkError } from '$lib/types/global'; + +import { has } from './has'; import { isNetworkError } from './is-network-error'; -import type { APIErrorResponse } from './request-from-api'; +import type { APIErrorResponse, TemporalAPIError } from './request-from-api'; import { routeForLoginPage } from './route-for'; -// This will eventually be expanded on. +interface NetworkErrorWithReport extends NetworkError { + report?: boolean; +} + export const handleError = ( - error: any, - notifications = notificationStore, + error: unknown, + toasts = toaster, errors = networkError, - isBrowser = browser, + isBrowser = BROWSER, ): void => { + if (error instanceof DOMException && error.name === 'AbortError') { + return; + } + + if (typeof error === 'string') { + toasts.push({ variant: 'error', message: error }); + } else { + (error as NetworkErrorWithReport).report = false; + } + + if (error instanceof Error) { + toasts.push({ variant: 'error', message: error.message }); + } + if (isUnauthorized(error) && isBrowser) { window.location.assign(routeForLoginPage(error?.message)); - return; } if (isForbidden(error) && isBrowser) { window.location.assign(routeForLoginPage(error?.message)); - return; } if (isNetworkError(error)) { - notifications.add('error', `${error.statusCode} ${error.statusText}`); + toasts.push({ + variant: 'error', + message: `${error.statusCode} ${error.statusText}`, + }); // Re-throw error to prevent other code from attempting to render errors.set(error); - throw error; - } - - if (typeof error === 'string') { - notifications.add('error', error); } - if (error instanceof Error) { - notifications.add('error', error.message); - } + throw error; }; export const handleUnauthorizedOrForbiddenError = ( error: APIErrorResponse, - isBrowser = browser, + isBrowser = BROWSER, ): void => { const msg = `${error?.status} ${error?.body?.message}`; @@ -55,10 +70,25 @@ export const handleUnauthorizedOrForbiddenError = ( } }; -export const isUnauthorized = (error: any): boolean => { - return error?.statusCode === 401 || error?.status === 401; +export const isUnauthorized = (error: unknown): error is TemporalAPIError => { + return hasStatusCode(error, 401); }; -export const isForbidden = (error: any): boolean => { - return error?.statusCode === 403 || error?.status === 403; +export const isForbidden = (error: unknown): error is TemporalAPIError => { + return hasStatusCode(error, 403); +}; + +const hasStatusCode = ( + error: unknown, + statusCode: number | string, +): error is TemporalAPIError => { + if (has(error, 'statusCode')) { + return error.statusCode === statusCode; + } + + if (has(error, 'status')) { + return error.status === statusCode; + } + + return false; }; diff --git a/src/lib/utilities/has.test.ts b/src/lib/utilities/has.test.ts index 3db3d54e3..e551f4f71 100644 --- a/src/lib/utilities/has.test.ts +++ b/src/lib/utilities/has.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { has, hasKeys } from './has'; + +import { has, hasAnyProperties } from './has'; describe('has', () => { it('returns true if an object has a key', () => { @@ -11,38 +12,73 @@ describe('has', () => { const source = { foo: 123 }; expect(has(source, 'bar')).toBe(false); }); + + it('returns true if an object has a multiple keys', () => { + const source = { foo: 123, bar: 456 }; + expect(has(source, 'foo', 'bar')).toBe(true); + }); + + it('returns false if an object has a no keys', () => { + const source = {}; + expect(has(source, 'foo', 'bar')).toBe(false); + }); + + it('returns false if an object does not have the required keys', () => { + const source = { foo: 123 }; + expect(has(source, 'foo', 'bar')).toBe(false); + }); + + it('returns false if an object has no keys', () => { + const source = {}; + expect(has(source)).toBe(false); + }); + + it('should return false if given null', () => { + expect(has(null)).toBe(false); + }); + + it('should return false if given undefined', () => { + expect(has(undefined)).toBe(false); + }); + + it('should return false if given a number', () => { + expect(has(3)).toBe(false); + }); + + it('should return false if given a boolean', () => { + expect(has(true)).toBe(false); + }); }); -describe('hasKeys', () => { - it('returns true if an object has keys', () => { +describe('hasAnyProperties', () => { + it('returns true if an object has enumerable properties', () => { const source = { foo: 123 }; - expect(hasKeys(source)).toBe(true); + expect(hasAnyProperties(source)).toBe(true); + }); + + it('returns true if an object has non-enumerable properties', () => { + const error = new Error(); + expect(hasAnyProperties(error)).toBe(true); }); it('returns false if an object has no keys', () => { const source = {}; - expect(hasKeys(source)).toBe(false); + expect(hasAnyProperties(source)).toBe(false); }); it('should return false if given null', () => { - expect(hasKeys(null as unknown as Parameters[0])).toBe( - false, - ); + expect(hasAnyProperties(null)).toBe(false); }); it('should return false if given undefined', () => { - expect(hasKeys(undefined as unknown as Parameters[0])).toBe( - false, - ); + expect(hasAnyProperties(undefined)).toBe(false); }); it('should return false if given a number', () => { - expect(hasKeys(3 as unknown as Parameters[0])).toBe(false); + expect(hasAnyProperties(3)).toBe(false); }); it('should return false if given a boolean', () => { - expect(hasKeys(true as unknown as Parameters[0])).toBe( - false, - ); + expect(hasAnyProperties(true)).toBe(false); }); }); diff --git a/src/lib/utilities/has.ts b/src/lib/utilities/has.ts index 395193bab..bf347259f 100644 --- a/src/lib/utilities/has.ts +++ b/src/lib/utilities/has.ts @@ -1,12 +1,19 @@ import { isObject } from './is'; -export const has = (target: unknown, property: string): boolean => { - return Object.prototype.hasOwnProperty.call(target, property); +export const has = , V = unknown>( + target: unknown, + ...properties: K +): target is Record => { + if (!hasAnyProperties(target)) return false; + for (const property of properties) { + if (!Object.prototype.hasOwnProperty.call(target, property)) return false; + } + return true; }; -export const hasKeys = (obj: { - [key: string | number | symbol]: unknown; -}): boolean => { +export const hasAnyProperties = ( + obj: unknown, +): obj is ReturnType => { if (!isObject(obj)) return false; - return !!Object.keys(obj).length; + return !!Object.getOwnPropertyNames(obj).length; }; diff --git a/src/lib/utilities/is-authorized.test.ts b/src/lib/utilities/is-authorized.test.ts index beb42bf1a..cb25b5507 100644 --- a/src/lib/utilities/is-authorized.test.ts +++ b/src/lib/utilities/is-authorized.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { isAuthorized } from './is-authorized'; const user = { diff --git a/src/lib/utilities/is-authorized.ts b/src/lib/utilities/is-authorized.ts index c1e87362c..7b1b06b91 100644 --- a/src/lib/utilities/is-authorized.ts +++ b/src/lib/utilities/is-authorized.ts @@ -1,3 +1,5 @@ +import type { Settings, User } from '$lib/types/global'; + export const isAuthorized = (settings: Settings, user: User): boolean => { return !settings.auth.enabled || Boolean(user?.accessToken); }; diff --git a/src/lib/utilities/is-event-type.test.ts b/src/lib/utilities/is-event-type.test.ts new file mode 100644 index 000000000..b85aae008 --- /dev/null +++ b/src/lib/utilities/is-event-type.test.ts @@ -0,0 +1,59 @@ +// eslint-disable-next-line @typescript-eslint/triple-slash-reference +/// + +import { expect } from 'vitest'; +import { describe, it } from 'vitest'; + +import type { EventType } from './is-event-type'; +import { isLocalActivityMarkerEvent } from './is-event-type'; +import { toEvent } from '../models/event-history'; + +const baseEvent = { + id: '1', + eventTime: '2023-10-13T14:50:18.784547Z', + version: '0', + taskId: '28312355', +} as const; + +const workflowTaskStarted: EventType = 'WorkflowTaskStarted'; + +describe('isLocalActivityMarkerEvent', () => { + it('should return false if it is not a MarkerRecordedEvent', () => { + const event = toEvent({ + ...baseEvent, + eventType: workflowTaskStarted, + workflowTaskStartedEventAttributes: { + scheduledEventId: '10', + identity: '50509@MacBook-Pro-2.lan1@', + requestId: 'ba23ccc5-86f1-46cb-9a6b-a578b2d66ed8', + }, + }); + + expect(isLocalActivityMarkerEvent(event)).toBe(false); + }); + + it('should return false if the event does not have "markerRecordedEventAttributes"', () => { + const event = toEvent({ + ...baseEvent, + eventType: 'MarkerRecorded', + workflowTaskStartedEventAttributes: { + scheduledEventId: '10', + identity: '50509@MacBook-Pro-2.lan1@', + requestId: 'ba23ccc5-86f1-46cb-9a6b-a578b2d66ed8', + }, + }); + + expect(isLocalActivityMarkerEvent(event)).toBe(false); + }); + + it('should return false the markerName is not "LocalActivity"', () => { + const event = toEvent({ + ...baseEvent, + eventType: 'MarkerRecorded', + markerRecordedEventAttributes: { + markerName: 'Version', + }, + }); + expect(isLocalActivityMarkerEvent(event)).toBe(false); + }); +}); diff --git a/src/lib/utilities/is-event-type.ts b/src/lib/utilities/is-event-type.ts index b26d2690a..cd6c22ac0 100644 --- a/src/lib/utilities/is-event-type.ts +++ b/src/lib/utilities/is-event-type.ts @@ -1,4 +1,66 @@ -export type ActivityType = typeof activityEvents[number]; +import type { + ActivityTaskCanceledEvent, + ActivityTaskCancelRequestedEvent, + ActivityTaskCompletedEvent, + ActivityTaskFailedEvent, + ActivityTaskScheduledEvent, + ActivityTaskStartedEvent, + ActivityTaskTimedOutEvent, + ChildWorkflowExecutionCanceledEvent, + ChildWorkflowExecutionCompletedEvent, + ChildWorkflowExecutionFailedEvent, + ChildWorkflowExecutionStartedEvent, + ChildWorkflowExecutionTerminatedEvent, + ChildWorkflowExecutionTimedOutEvent, + CommonHistoryEvent, + EventAttribute, + EventAttributeKey, + EventWithAttributes, + ExternalWorkflowExecutionCancelRequestedEvent, + ExternalWorkflowExecutionSignaledEvent, + HistoryEvent, + IterableEvent, + MarkerRecordedEvent, + NexusOperationCanceledEvent, + NexusOperationCancelRequestedEvent, + NexusOperationCompletedEvent, + NexusOperationFailedEvent, + NexusOperationScheduledEvent, + NexusOperationStartedEvent, + NexusOperationTimedOutEvent, + RequestCancelExternalWorkflowExecutionFailedEvent, + RequestCancelExternalWorkflowExecutionInitiatedEvent, + SignalExternalWorkflowExecutionFailedEvent, + SignalExternalWorkflowExecutionInitiatedEvent, + StartChildWorkflowExecutionFailedEvent, + StartChildWorkflowExecutionInitiatedEvent, + TimerCanceledEvent, + TimerFiredEvent, + TimerStartedEvent, + UpsertWorkflowSearchAttributesEvent, + WorkflowEvent, + WorkflowExecutionCanceledEvent, + WorkflowExecutionCancelRequestedEvent, + WorkflowExecutionCompletedEvent, + WorkflowExecutionContinuedAsNewEvent, + WorkflowExecutionFailedEvent, + WorkflowExecutionOptionsUpdatedEvent, + WorkflowExecutionSignaledEvent, + WorkflowExecutionStartedEvent, + WorkflowExecutionTerminatedEvent, + WorkflowExecutionTimedOutEvent, + WorkflowExecutionUpdateAcceptedEvent, + WorkflowExecutionUpdateAdmittedEvent, + WorkflowExecutionUpdateCompletedEvent, + WorkflowExecutionUpdateRejectedEvent, + WorkflowTaskCompletedEvent, + WorkflowTaskFailedEvent, + WorkflowTaskScheduledEvent, + WorkflowTaskStartedEvent, + WorkflowTaskTimedOutEvent, +} from '$lib/types/events'; + +export type ActivityType = (typeof activityEvents)[number]; export const activityEvents = [ 'ActivityTaskCanceled', 'ActivityTaskCancelRequested', @@ -9,21 +71,21 @@ export const activityEvents = [ 'ActivityTaskTimedOut', ] as const; -export type TimerType = typeof timerEvents[number]; +export type TimerType = (typeof timerEvents)[number]; export const timerEvents = [ 'TimerStarted', 'TimerCanceled', 'TimerFired', ] as const; -export type SignalType = typeof signalEvents[number]; +export type SignalType = (typeof signalEvents)[number]; export const signalEvents = [ 'WorkflowExecutionSignaled', 'SignalExternalWorkflowExecutionFailed', 'SignalExternalWorkflowExecutionInitiated', ] as const; -export type MarkerType = typeof markerEvents[number]; +export type MarkerType = (typeof markerEvents)[number]; export const markerEvents = ['MarkerRecorded'] as const; const childEvents = [ @@ -37,13 +99,26 @@ const childEvents = [ 'StartChildWorkflowExecutionFailed', ] as const; -export type EventType = typeof eventTypes[number]; +const nexusEvents = [ + 'NexusOperationScheduled', + 'NexusOperationStarted', + 'NexusOperationCompleted', + 'NexusOperationFailed', + 'NexusOperationCanceled', + 'NexusOperationTimedOut', + 'NexusOperationCancelRequested', + 'NexusOperationCancelRequestCompleted', + 'NexusOperationCancelRequestFailed', +] as const; + +export type EventType = (typeof eventTypes)[number]; export const eventTypes = [ ...activityEvents, ...timerEvents, ...signalEvents, ...markerEvents, ...childEvents, + ...nexusEvents, 'WorkflowExecutionCanceled', 'WorkflowExecutionCancelRequested', 'WorkflowExecutionCompleted', @@ -52,6 +127,7 @@ export const eventTypes = [ 'WorkflowExecutionStarted', 'WorkflowExecutionTerminated', 'WorkflowExecutionTimedOut', + 'WorkflowExecutionOptionsUpdated', 'WorkflowTaskCompleted', 'WorkflowTaskFailed', 'WorkflowTaskScheduled', @@ -62,6 +138,12 @@ export const eventTypes = [ 'RequestCancelExternalWorkflowExecutionFailed', 'RequestCancelExternalWorkflowExecutionInitiated', 'UpsertWorkflowSearchAttributes', + 'WorkflowExecutionUpdateAccepted', + 'WorkflowExecutionUpdateAdmitted', + 'WorkflowExecutionUpdateCompleted', + 'WorkflowExecutionUpdateRejected', + 'WorkflowExecutionUpdateRequested', + 'WorkflowPropertiesModified', ] as const; export const eventAttributeKeys: Readonly = [ @@ -69,6 +151,7 @@ export const eventAttributeKeys: Readonly = [ 'workflowExecutionCompletedEventAttributes', 'workflowExecutionFailedEventAttributes', 'workflowExecutionTimedOutEventAttributes', + 'workflowExecutionOptionsUpdatedEventAttributes' as unknown as EventAttributeKey, 'workflowTaskStartedEventAttributes', 'workflowTaskScheduledEventAttributes', 'workflowTaskCompletedEventAttributes', @@ -87,6 +170,10 @@ export const eventAttributeKeys: Readonly = [ 'markerRecordedEventAttributes', 'workflowExecutionSignaledEventAttributes', 'workflowExecutionTerminatedEventAttributes', + 'workflowExecutionUpdateAdmittedEventAttributes', + 'workflowExecutionUpdateAcceptedEventAttributes', + 'workflowExecutionUpdateCompletedEventAttributes', + 'workflowExecutionUpdateRejectedEventAttributes', 'workflowExecutionCancelRequestedEventAttributes', 'workflowExecutionCanceledEventAttributes', 'requestCancelExternalWorkflowExecutionInitiatedEventAttributes', @@ -105,8 +192,26 @@ export const eventAttributeKeys: Readonly = [ 'signalExternalWorkflowExecutionFailedEventAttributes', 'externalWorkflowExecutionSignaledEventAttributes', 'upsertWorkflowSearchAttributesEventAttributes', + 'nexusOperationScheduledEventAttributes', + 'nexusOperationStartedEventAttributes', + 'nexusOperationCompletedEventAttributes', + 'nexusOperationFailedEventAttributes', + 'nexusOperationCanceledEventAttributes', + 'nexusOperationTimedOutEventAttributes', + 'nexusOperationCancelRequestedEventAttributes', + 'nexusOperationCancelRequestCompletedEventAttributes' as unknown as EventAttributeKey, + 'nexusOperationCancelRequestFailedEventAttributes' as unknown as EventAttributeKey, + 'workflowExecutionOptionsUpdatedEventAttributes' as unknown as EventAttributeKey, + 'workflowPropertiesModifiedEventAttributes', ] as const; +export type ResetEventType = (typeof validResetEventTypes)[number]; +export const validResetEventTypes = [ + 'WorkflowTaskCompleted', + 'WorkflowTaskFailed', + 'WorkflowTaskTimedOut', +]; + export const findAttributeKey = (event: HistoryEvent): EventAttributeKey => { for (const key of eventAttributeKeys) { if (key in event) return key; @@ -131,8 +236,10 @@ export const findAttributesAndKey = ( const hasAttributes = >(key: EventAttributeKey) => - (event: CommonHistoryEvent): event is T => { - return Boolean(event[key]); + ( + event: IterableEvent | CommonHistoryEvent | HistoryEvent | undefined, + ): event is T => { + return Boolean(event?.[key]); }; export const isWorkflowExecutionStartedEvent = @@ -173,9 +280,17 @@ export const isWorkflowTaskTimedOutEvent = 'workflowTaskTimedOutEventAttributes', ); -export const isWorkflowTaskFailedEvent = hasAttributes( - 'workflowTaskFailedEventAttributes', -); +export const isPureWorkflowTaskFailedEvent = + hasAttributes('workflowTaskFailedEventAttributes'); + +export const isWorkflowTaskFailedEvent = (event: WorkflowEvent) => { + return ( + isPureWorkflowTaskFailedEvent(event) && + event.workflowTaskFailedEventAttributes?.failure?.message !== + 'UnhandledCommand' && + !event.workflowTaskFailedEventAttributes?.failure?.resetWorkflowFailureInfo + ); +}; export const isActivityTaskScheduledEvent = hasAttributes( @@ -225,6 +340,11 @@ export const isMarkerRecordedEvent = hasAttributes( 'markerRecordedEventAttributes', ); +export const isWorkflowExecutionOptionsUpdatedEvent = + hasAttributes( + 'workflowExecutionOptionsUpdatedEventAttributes', + ); + export const isWorkflowExecutionSignaledEvent = hasAttributes( 'workflowExecutionSignaledEventAttributes', @@ -325,12 +445,87 @@ export const isUpsertWorkflowSearchAttributesEvent = 'upsertWorkflowSearchAttributesEventAttributes', ); -export const isLocalActivityMarkerEvent = (event) => { - const payload: any = - event?.markerRecordedEventAttributes?.details?.data?.payloads?.[0]; - return ( - isMarkerRecordedEvent(event) && - event?.markerRecordedEventAttributes?.markerName === 'LocalActivity' && - Boolean(payload?.ActivityType ?? payload?.activity_type) - ); +export const isResetEvent = (event: WorkflowEvent): boolean => { + return validResetEventTypes.includes(event.eventType); }; + +const localActivityMarkerNames = ['LocalActivity', 'core_local_activity']; + +export const isLocalActivityMarkerEvent = ( + event: IterableEvent | CommonHistoryEvent, +) => { + if (!isMarkerRecordedEvent(event)) return false; + + if ( + !localActivityMarkerNames.includes( + event.markerRecordedEventAttributes.markerName, + ) + ) { + return false; + } + + return true; +}; + +export const isWorkflowExecutionUpdateAcceptedEvent = + hasAttributes( + 'workflowExecutionUpdateAcceptedEventAttributes', + ); + +export const isWorkflowExecutionUpdateRejectedEvent = + hasAttributes( + 'workflowExecutionUpdateRejectedEventAttributes', + ); + +export const isWorkflowExecutionUpdateAdmittedEvent = + hasAttributes( + 'workflowExecutionUpdateAdmittedEventAttributes', + ); + +export const isWorkflowExecutionUpdateCompletedEvent = + hasAttributes( + 'workflowExecutionUpdateCompletedEventAttributes', + ); + +export const isFailedWorkflowExecutionUpdateCompletedEvent = ( + event: WorkflowEvent, +): boolean => + isWorkflowExecutionUpdateCompletedEvent(event) && + Boolean( + event.workflowExecutionUpdateCompletedEventAttributes.outcome?.failure, + ); + +export const isNexusOperationScheduledEvent = + hasAttributes( + 'nexusOperationScheduledEventAttributes', + ); + +export const isNexusOperationStartedEvent = + hasAttributes( + 'nexusOperationStartedEventAttributes', + ); + +export const isNexusOperationCompletedEvent = + hasAttributes( + 'nexusOperationCompletedEventAttributes', + ); + +export const isNexusOperationFailedEvent = + hasAttributes( + 'nexusOperationFailedEventAttributes', + ); + +export const isNexusOperationCanceledEvent = + hasAttributes( + 'nexusOperationCanceledEventAttributes', + ); + +export const isNexusOperationTimedOutEvent = + hasAttributes( + 'nexusOperationTimedOutEventAttributes', + ); + +export const isNexusOperationCancelRequestedEvent = + hasAttributes( + 'nexusOperationCancelRequestedEventAttributes', + ); diff --git a/src/lib/utilities/is-function.test.ts b/src/lib/utilities/is-function.test.ts index bd7144846..e7d30f201 100644 --- a/src/lib/utilities/is-function.test.ts +++ b/src/lib/utilities/is-function.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { isFunction } from './is-function'; describe('isFunction', () => { diff --git a/src/lib/utilities/is-http.test.ts b/src/lib/utilities/is-http.test.ts index 16487f261..d3b3ae799 100644 --- a/src/lib/utilities/is-http.test.ts +++ b/src/lib/utilities/is-http.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { validateHttps, validateHttp, validateHttpOrHttps } from './is-http'; + +import { validateHttp, validateHttpOrHttps, validateHttps } from './is-http'; describe('validateHttps', () => { it('Should return true with valid https endpoint', () => { diff --git a/src/lib/utilities/is-http.ts b/src/lib/utilities/is-http.ts index af01e31be..85b98d982 100644 --- a/src/lib/utilities/is-http.ts +++ b/src/lib/utilities/is-http.ts @@ -1,9 +1,9 @@ export const validateHttps = (endpoint: string): boolean => { - return endpoint.substring(0, 8) === 'https://'; + return endpoint?.substring(0, 8) === 'https://'; }; export const validateHttp = (endpoint: string): boolean => { - return endpoint.substring(0, 7) === 'http://'; + return endpoint?.substring(0, 7) === 'http://'; }; export const validateHttpOrHttps = (endpoint: string): boolean => { diff --git a/src/lib/utilities/is-network-error.test.ts b/src/lib/utilities/is-network-error.test.ts index 60c75af81..ac4d7fc76 100644 --- a/src/lib/utilities/is-network-error.test.ts +++ b/src/lib/utilities/is-network-error.test.ts @@ -1,20 +1,22 @@ import { describe, expect, it } from 'vitest'; + import { isNetworkError } from './is-network-error'; -const networkErr = { +const networkError = { statusCode: 200, - statusText: "Error'd", + statusText: 'Error', response: {}, }; describe('isNetworkError', () => { it('Should infer a networkError error with the correct shape', () => { try { - throw networkErr; + throw networkError; } catch (err) { expect(isNetworkError(err)).toBeTruthy(); } }); + it('Should not infer a networkError error with the incorrecrt shape', () => { try { throw { somethingElse: '' }; @@ -22,6 +24,7 @@ describe('isNetworkError', () => { expect(isNetworkError(err)).toBeFalsy(); } }); + it("Should not infer a networkError when it's a js error", () => { try { throw new Error('error'); @@ -29,6 +32,7 @@ describe('isNetworkError', () => { expect(isNetworkError(err)).toBeFalsy(); } }); + it("Should not infer a networkError when it's a promise", async () => { try { await Promise.reject(); diff --git a/src/lib/utilities/is-network-error.ts b/src/lib/utilities/is-network-error.ts index 7c5f1c363..9311f1744 100644 --- a/src/lib/utilities/is-network-error.ts +++ b/src/lib/utilities/is-network-error.ts @@ -1,10 +1,10 @@ +import type { NetworkError } from '$lib/types/global'; + +import { has } from './has'; + export function isNetworkError( error: unknown | NetworkError, ): error is NetworkError { - const networkErr = error as NetworkError; - return ( - networkErr?.statusCode !== undefined && - networkErr?.statusText !== undefined && - networkErr?.response !== undefined - ); + if (!error) return false; + return has(error, 'statusCode', 'statusText', 'response'); } diff --git a/src/lib/utilities/is-pending-activity.test.ts b/src/lib/utilities/is-pending-activity.test.ts index 2e78f90f9..d70d5c235 100644 --- a/src/lib/utilities/is-pending-activity.test.ts +++ b/src/lib/utilities/is-pending-activity.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { isPendingActivity } from './is-pending-activity'; describe('isPendingActivity', () => { diff --git a/src/lib/utilities/is-pending-activity.ts b/src/lib/utilities/is-pending-activity.ts index 5fda86c07..1d54364f7 100644 --- a/src/lib/utilities/is-pending-activity.ts +++ b/src/lib/utilities/is-pending-activity.ts @@ -1,3 +1,5 @@ +import type { PendingActivity, PendingNexusOperation } from '$lib/types/events'; + import { has } from './has'; export const isPendingActivity = (event: unknown): event is PendingActivity => { @@ -7,3 +9,13 @@ export const isPendingActivity = (event: unknown): event is PendingActivity => { if (has(event, 'activityType')) return true; return false; }; + +export const isPendingNexusOperation = ( + event: unknown, +): event is PendingNexusOperation => { + if (event === null) return false; + if (typeof event !== 'object') return false; + if (Array.isArray(event)) return false; + if (has(event, 'operation') && has(event, 'endpoint')) return true; + return false; +}; diff --git a/src/lib/utilities/is-subrow-activity.ts b/src/lib/utilities/is-subrow-activity.ts deleted file mode 100644 index d6a32e6a8..000000000 --- a/src/lib/utilities/is-subrow-activity.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { has } from './has'; - -export const isSubrowActivity = (event: IterableEvent): boolean => { - return has(event?.attributes, 'workflowTaskCompletedEventId'); -}; diff --git a/src/lib/utilities/is.test.ts b/src/lib/utilities/is.test.ts index 8eb149945..f3210da06 100644 --- a/src/lib/utilities/is.test.ts +++ b/src/lib/utilities/is.test.ts @@ -1,14 +1,16 @@ import { describe, expect, it } from 'vitest'; + import { - isString, + isExecutionStatus, isNull, - isObject, + isNullConditional, isNumber, - isSortOrder, + isObject, isOperator, - isExecutionStatus, - isSpace, isQuote, + isSortOrder, + isSpace, + isString, } from './is'; describe('isString', () => { @@ -317,15 +319,15 @@ describe('isSpace', () => { describe('isQuote', () => { it('should return true for a single quote', () => { - expect(isQuote(`'`)).toBe(true); + expect(isQuote("'")).toBe(true); }); it('should return true for a single quote', () => { - expect(isQuote(`"`)).toBe(true); + expect(isQuote('"')).toBe(true); }); it('should return false for a letter', () => { - expect(isQuote(`a`)).toBe(false); + expect(isQuote('a')).toBe(false); }); it('should return false for null', () => { @@ -336,3 +338,19 @@ describe('isQuote', () => { expect(isQuote(undefined)).toBe(false); }); }); + +describe('isNullConditional', () => { + it('should return true for is', () => { + expect(isNullConditional('IS')).toBe(true); + expect(isNullConditional('is')).toBe(true); + }); + + it('should return true for is not', () => { + expect(isNullConditional('IS NOT')).toBe(true); + expect(isNullConditional('is not')).toBe(true); + }); + + it('should return false for null', () => { + expect(isNullConditional(null)).toBe(false); + }); +}); diff --git a/src/lib/utilities/is.ts b/src/lib/utilities/is.ts index bcdebc46b..5e8790ff9 100644 --- a/src/lib/utilities/is.ts +++ b/src/lib/utilities/is.ts @@ -1,11 +1,17 @@ import type { EventSortOrder } from '$lib/stores/event-view'; +import type { WorkflowExecutionStatus } from '$lib/types'; +import type { WorkflowStatus } from '$lib/types/workflows'; + +import { has } from './has'; type Space = ' '; type Quote = "'" | '"'; -type Operator = typeof operators[number]; -type Conditional = typeof conditionals[number]; -type Parenthesis = typeof parenthesis[number]; -type Join = typeof joins[number]; +type Backtick = '`'; +type Operator = (typeof operators)[number]; +type Conditional = (typeof conditionals)[number]; +type Parenthesis = (typeof parenthesis)[number]; +type EndParenthesis = ')'; +type Join = (typeof joins)[number]; const executionStatuses: Readonly = [ 'Running', @@ -36,6 +42,7 @@ const operators = [ 'in', '(', ')', + 'starts_with', ] as const; const conditionals = [ @@ -49,6 +56,10 @@ const conditionals = [ '>', '<', '!', + 'starts_with', + 'is', + 'is not', + 'in', ] as const; const joins = ['and', 'or'] as const; @@ -59,7 +70,7 @@ export const isString = (x: unknown): x is string => typeof x === 'string'; export const isNull = (x: unknown): x is null => x === null; -export const isObject = (x: unknown): x is { unknown: unknown } => { +export const isObject = (x: unknown): x is Record => { if (isNull(x)) return false; if (Array.isArray(x)) return false; if (typeof x === 'object') return true; @@ -91,6 +102,10 @@ export const isQuote = (x: unknown): x is Quote => { return false; }; +export const isBacktick = (x: unknown): x is Backtick => { + return x === '`'; +}; + export const isOperator = (x: unknown): x is Operator => { if (!isString(x)) return false; x = x.toLocaleLowerCase(); @@ -124,6 +139,11 @@ export const isParenthesis = (x: unknown): x is Parenthesis => { return false; }; +export const isEndParenthesis = (x: unknown): x is EndParenthesis => { + if (!isString(x)) return false; + return x.toLocaleLowerCase() === ')'; +}; + export const isJoin = (x: unknown): x is Join => { if (!isString(x)) return false; x = x.toLocaleLowerCase(); @@ -142,6 +162,13 @@ export const isBetween = (x: unknown) => { return x === 'between'; }; +export const isNullConditional = (x: unknown) => { + if (!isString(x)) return false; + x = x.toLocaleLowerCase(); + + return x === 'is' || x === 'is not'; +}; + export const isSortOrder = ( sortOrder: string | EventSortOrder, ): sortOrder is EventSortOrder => { @@ -149,3 +176,18 @@ export const isSortOrder = ( if (sortOrder === 'descending') return true; return false; }; + +export const isError = (e: unknown): e is Error => { + return has(e, 'name', 'message'); +}; + +export const isStartsWith = (x: unknown) => { + if (!isString(x)) return false; + return x.toLocaleLowerCase() === 'starts_with'; +}; + +export const isInConditional = (x: unknown) => { + if (!isString(x)) return false; + + return x.toLocaleLowerCase() === 'in'; +}; diff --git a/src/lib/utilities/merge.test.ts b/src/lib/utilities/merge.test.ts index b8a193856..a6ca8b49f 100644 --- a/src/lib/utilities/merge.test.ts +++ b/src/lib/utilities/merge.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { merge } from './merge'; describe('merge', () => { diff --git a/src/lib/utilities/namespace-url-pattern.test.ts b/src/lib/utilities/namespace-url-pattern.test.ts index 14a070ff8..6be260ebf 100644 --- a/src/lib/utilities/namespace-url-pattern.test.ts +++ b/src/lib/utilities/namespace-url-pattern.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { namespaceUrlPattern } from './namespace-url-pattern'; describe('Namespace Url Pattern', () => { diff --git a/src/lib/utilities/namespace-url-pattern.ts b/src/lib/utilities/namespace-url-pattern.ts index d692d153b..73fb99982 100644 --- a/src/lib/utilities/namespace-url-pattern.ts +++ b/src/lib/utilities/namespace-url-pattern.ts @@ -7,3 +7,8 @@ export const namespaceUrlPattern = new UrlPattern( '/namespaces/:namespace/*', urlPatternOpts, ); + +export const workflowRoutePattern = new UrlPattern( + '/namespaces/:namespace/workflows*', + urlPatternOpts, +); diff --git a/src/lib/utilities/nexus-enabled.ts b/src/lib/utilities/nexus-enabled.ts new file mode 100644 index 000000000..d87cf50f4 --- /dev/null +++ b/src/lib/utilities/nexus-enabled.ts @@ -0,0 +1,5 @@ +import type { Capabilities } from '$lib/types'; + +export const nexusEnabled = (capabilities: Capabilities): boolean => { + return capabilities?.nexus; +}; diff --git a/src/lib/utilities/omit.test.ts b/src/lib/utilities/omit.test.ts index d2909ceb0..a28fb0f96 100644 --- a/src/lib/utilities/omit.test.ts +++ b/src/lib/utilities/omit.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { omit } from './omit'; describe('omit', () => { diff --git a/src/lib/utilities/omit.ts b/src/lib/utilities/omit.ts index 7bb97441f..34416776f 100644 --- a/src/lib/utilities/omit.ts +++ b/src/lib/utilities/omit.ts @@ -13,7 +13,7 @@ type Omit = { export const omit: Omit = (object, ...keys) => { const result = {} as { - [K in keyof typeof object]: typeof object[K]; + [K in keyof typeof object]: (typeof object)[K]; }; for (const key of Object.keys(object)) { diff --git a/src/lib/utilities/paginated.test.ts b/src/lib/utilities/paginated.test.ts index f4ba0f1c2..9d0f896ad 100644 --- a/src/lib/utilities/paginated.test.ts +++ b/src/lib/utilities/paginated.test.ts @@ -1,4 +1,5 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; + import { paginated } from './paginated'; const getPage = async (token: string | Uint8Array | null = null) => { diff --git a/src/lib/utilities/paginated.ts b/src/lib/utilities/paginated.ts index aa4093063..eaf49446c 100644 --- a/src/lib/utilities/paginated.ts +++ b/src/lib/utilities/paginated.ts @@ -1,3 +1,10 @@ +import type { + NextPageToken, + PaginationCallbacks, + WithNextPageToken, + WithoutNextPageToken, +} from '$lib/types/global'; + import { handleError } from './handle-error'; import { isFunction } from './is-function'; import { merge } from './merge'; @@ -34,23 +41,25 @@ export const paginated = async ( try { const response = await fn(token); - const { nextPageToken, ...props } = response; - const mergedProps = merge(previousProps, props); + if (response) { + const { nextPageToken, ...props } = response; + const mergedProps = merge(previousProps, props); - if (isFunction(onUpdate)) onUpdate(mergedProps, props); + if (isFunction(onUpdate)) onUpdate(mergedProps, props); - if (!nextPageToken) { - if (isFunction(onComplete)) onComplete(mergedProps); - return mergedProps; - } + if (!nextPageToken) { + if (isFunction(onComplete)) onComplete(mergedProps); + return mergedProps; + } - return paginated(fn, { - onStart, - onUpdate, - onComplete, - token: nextPageToken, - previousProps: mergedProps, - }); + return paginated(fn, { + onStart, + onUpdate, + onComplete, + token: nextPageToken, + previousProps: mergedProps, + }); + } } catch (error: unknown) { onError(error); } diff --git a/src/lib/utilities/parse-with-big-int.test.ts b/src/lib/utilities/parse-with-big-int.test.ts new file mode 100644 index 000000000..2486f1e4b --- /dev/null +++ b/src/lib/utilities/parse-with-big-int.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from 'vitest'; + +import { parseWithBigInt } from './parse-with-big-int'; + +describe('parseWithBigInt', () => { + it('should return parsed number', () => { + const content = '13939393'; + const parsed = parseWithBigInt(content); + expect(parsed).toBe(13939393); + }); + it('should return parsed BigInt', () => { + const content = '58585858585858585858585'; + const parsed = parseWithBigInt(content); + expect(parsed).toBe(58585858585858585858585n); + }); + it('should return parsed decimal', () => { + const content = '4.552'; + const parsed = parseWithBigInt(content); + expect(parsed).toBe(4.552); + }); + it('should return parsed long decimal', () => { + // We lose precison after 15 digits + const content = '4.55293882930339339293'; + const parsed = parseWithBigInt(content); + expect(parsed).toBe(4.552938829303393); + }); +}); diff --git a/src/lib/utilities/parse-with-big-int.ts b/src/lib/utilities/parse-with-big-int.ts index 51f9bdad5..999e7a324 100644 --- a/src/lib/utilities/parse-with-big-int.ts +++ b/src/lib/utilities/parse-with-big-int.ts @@ -1,12 +1,20 @@ import JSONbig from 'json-bigint'; -export const parseWithBigInt = (content: string) => - JSONbig.parse(content, { - useNativeBigInt: true, - }); +const JSONBigNative = JSONbig({ + useNativeBigInt: true, + constructorAction: 'preserve', +}); -export const stringifyWithBigInt = ( - value: any, - replacer?: (this: any, key: string, value: any) => any, +export const parseWithBigInt = (content: string) => { + try { + return JSONBigNative.parse(content); + } catch (e) { + return JSON.parse(content); + } +}; + +export const stringifyWithBigInt = ( + value: T, + replacer?: (key: string, value: T) => T, space?: string | number, -) => JSONbig.stringify(value, replacer, space); +) => JSONBigNative.stringify(value, replacer, space); diff --git a/src/lib/utilities/path-matches.test.ts b/src/lib/utilities/path-matches.test.ts index ce0656d19..13086c94d 100644 --- a/src/lib/utilities/path-matches.test.ts +++ b/src/lib/utilities/path-matches.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { pathMatches } from './path-matches'; describe('pathMatches', () => { diff --git a/src/lib/utilities/payload-to-string.test.ts b/src/lib/utilities/payload-to-string.test.ts new file mode 100644 index 000000000..508e68d57 --- /dev/null +++ b/src/lib/utilities/payload-to-string.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, it } from 'vitest'; + +import { payloadToString } from './payload-to-string'; + +describe('payloadToString', () => { + it('should combine payloads into a string if it is an array of payloads', () => { + expect(payloadToString(['a'])).toBe('a'); + expect(payloadToString(['a', 'b', 'c'])).toBe('a, b, c'); + }); + + it('should return the payload if the payload is not an array of payloads', () => { + expect(payloadToString('a')).toBe('a'); + expect(payloadToString(['a, b, c'])).toBe('a, b, c'); + }); +}); diff --git a/src/lib/utilities/payload-to-string.ts b/src/lib/utilities/payload-to-string.ts new file mode 100644 index 000000000..e9b778b43 --- /dev/null +++ b/src/lib/utilities/payload-to-string.ts @@ -0,0 +1,7 @@ +import type { Payload } from '$lib/types'; + +export const payloadToString = (value: Payload) => { + if (Array.isArray(value)) return value.join(', '); + + return value; +}; diff --git a/src/lib/utilities/pending-activities.test.ts b/src/lib/utilities/pending-activities.test.ts new file mode 100644 index 000000000..4b9c26e66 --- /dev/null +++ b/src/lib/utilities/pending-activities.test.ts @@ -0,0 +1,85 @@ +import { describe, expect, it } from 'vitest'; + +import { toEvent } from '$lib/models/event-history'; + +import { + getPendingActivity, + isAssociatedPendingActivity, +} from './pending-activities'; + +import pendingActivityWorkflow from '$fixtures/workflow.pending-activities.json'; + +const getScheduledEvent = (activityId: string) => + toEvent({ + eventId: '12', + eventTime: '2024-04-10T13:15:26.790496Z', + eventType: 'EVENT_TYPE_ACTIVITY_TASK_SCHEDULED', + taskId: '1048648', + activityTaskScheduledEventAttributes: { + activityId: activityId, + activityType: { + name: 'fun-activity', + }, + taskQueue: { + name: 'demo', + kind: 'TASK_QUEUE_KIND_NORMAL', + }, + header: {}, + input: { + payloads: [], + }, + scheduleToCloseTimeout: '0s', + scheduleToStartTimeout: '0s', + startToCloseTimeout: '1s', + heartbeatTimeout: '0s', + workflowTaskCompletedEventId: '10', + retryPolicy: { + initialInterval: '1s', + backoffCoefficient: 2, + maximumInterval: '5s', + }, + useCompatibleVersion: true, + }, + }); + +describe('getPendingActivity', () => { + it('should return pending activity if the event has activityId of same pending activityId', () => { + const pendingActivities = pendingActivityWorkflow.pendingActivities; + expect(getPendingActivity(getScheduledEvent('1'), pendingActivities)).toBe( + pendingActivities[0], + ); + }); + + it('should return undefined if the event has different activityId of pending activityId', () => { + const pendingActivities = pendingActivityWorkflow.pendingActivities; + expect(getPendingActivity(getScheduledEvent('23'), pendingActivities)).toBe( + undefined, + ); + }); +}); + +describe('isAssociatedPendingActivity', () => { + it('should return true if the event has matching activityId of pending activity', () => { + const pendingActivities = pendingActivityWorkflow.pendingActivities; + expect( + isAssociatedPendingActivity(getScheduledEvent('1'), pendingActivities[0]), + ).toBe(true); + }); + + it('should return undefined if the event has different activityId of pending activityId', () => { + const pendingActivities = pendingActivityWorkflow.pendingActivities; + expect( + isAssociatedPendingActivity(getScheduledEvent('1'), pendingActivities[1]), + ).toBe(false); + }); + + it('should return undefined if the event has different activityId of pending activityId', () => { + const pendingActivities = pendingActivityWorkflow.pendingActivities; + expect( + isAssociatedPendingActivity( + getScheduledEvent('16'), + pendingActivities[0], + ), + ).toBe(false); + }); +}); diff --git a/src/lib/utilities/pending-activities.ts b/src/lib/utilities/pending-activities.ts new file mode 100644 index 000000000..a3cb69b66 --- /dev/null +++ b/src/lib/utilities/pending-activities.ts @@ -0,0 +1,75 @@ +import type { EventGroup } from '$lib/models/event-groups/event-groups'; +import { isEvent } from '$lib/models/event-history'; +import type { + PendingActivity, + PendingNexusOperation, + WorkflowEventWithPending, +} from '$lib/types/events'; +import type { WorkflowEvent } from '$lib/types/events'; + +import { + isActivityTaskScheduledEvent, + isNexusOperationScheduledEvent, +} from './is-event-type'; +import { + isPendingActivity, + isPendingNexusOperation, +} from './is-pending-activity'; + +export const getPendingActivity = ( + event: WorkflowEvent, + pendingActivities: PendingActivity[], +): PendingActivity | undefined => { + let pendingActivity: PendingActivity | undefined; + + if (isActivityTaskScheduledEvent(event)) { + pendingActivity = pendingActivities.find( + (p) => p.activityId === event.attributes.activityId, + ); + } + + return pendingActivity; +}; + +export const isAssociatedPendingActivity = ( + event: WorkflowEvent, + pendingActivity: PendingActivity | undefined, +): boolean => { + if (isActivityTaskScheduledEvent(event) && pendingActivity) { + return pendingActivity.activityId === event.attributes.activityId; + } + + return false; +}; + +export const getPendingNexusOperation = ( + event: WorkflowEvent, + pendingNexusOperations: PendingNexusOperation[], +): PendingNexusOperation | undefined => { + let pendingOperation: PendingNexusOperation | undefined; + + if (isNexusOperationScheduledEvent(event)) { + pendingOperation = pendingNexusOperations.find( + (p) => p.scheduledEventId === event.id, + ); + } + + return pendingOperation; +}; + +export const getGroupForEventOrPendingEvent = ( + groups: EventGroup[], + event: WorkflowEventWithPending, +) => { + return groups.find((g) => { + if (isEvent(event)) { + return g.eventIds.has(event.id); + } else if (isPendingActivity(event)) { + return g.pendingActivity?.id === event.id; + } else if (isPendingNexusOperation(event)) { + return ( + g?.pendingNexusOperation?.scheduledEventId === event?.scheduledEventId + ); + } + }); +}; diff --git a/src/lib/utilities/pick.test.ts b/src/lib/utilities/pick.test.ts index 110f87df0..1d2ccfa4b 100644 --- a/src/lib/utilities/pick.test.ts +++ b/src/lib/utilities/pick.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { pick } from './pick'; describe('pick', () => { diff --git a/src/lib/utilities/query/filter-workflow-query.test.ts b/src/lib/utilities/query/filter-workflow-query.test.ts index e1da710a8..33833402b 100644 --- a/src/lib/utilities/query/filter-workflow-query.test.ts +++ b/src/lib/utilities/query/filter-workflow-query.test.ts @@ -1,6 +1,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { toListWorkflowQueryFromFilters } from './list-workflow-query'; -import { combineDropdownFilters } from './to-list-workflow-filters'; + +import { toListWorkflowQueryFromFilters } from './filter-workflow-query'; +import { combineFilters } from './to-list-workflow-filters'; +import { isVersionNewer } from '../version-check'; describe('toListWorkflowQueryFromFilters', () => { beforeEach(() => { @@ -21,15 +23,14 @@ describe('toListWorkflowQueryFromFilters', () => { const filters = [ { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', value: '', }, ]; - const query = toListWorkflowQueryFromFilters( - combineDropdownFilters(filters), - ); + const query = toListWorkflowQueryFromFilters(filters); expect(query).toBe(''); }); @@ -37,22 +38,22 @@ describe('toListWorkflowQueryFromFilters', () => { const filters = [ { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', value: 'Running', }, ]; - const query = toListWorkflowQueryFromFilters( - combineDropdownFilters(filters), - ); - expect(query).toBe('ExecutionStatus="Running"'); + const query = toListWorkflowQueryFromFilters(filters); + expect(query).toBe('`ExecutionStatus`="Running"'); }); it('should convert multiple ExecutionStatus filters', () => { const filters = [ { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: 'OR', parenthesis: '(', @@ -60,6 +61,7 @@ describe('toListWorkflowQueryFromFilters', () => { }, { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: 'OR', parenthesis: '', @@ -67,24 +69,39 @@ describe('toListWorkflowQueryFromFilters', () => { }, { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: '', parenthesis: ')', value: 'Failed', }, ]; - const query = toListWorkflowQueryFromFilters( - combineDropdownFilters(filters), - ); + const query = toListWorkflowQueryFromFilters(combineFilters(filters)); expect(query).toBe( - '(ExecutionStatus="Running" OR ExecutionStatus="Canceled" OR ExecutionStatus="Failed")', + '(`ExecutionStatus`="Running" OR `ExecutionStatus`="Canceled" OR `ExecutionStatus`="Failed")', ); }); + it('should convert a boolean filter', () => { + const filters = [ + { + attribute: 'CustomBoolField', + type: 'Bool', + conditional: '=', + operator: '', + parenthesis: '', + value: 'false', + }, + ]; + const query = toListWorkflowQueryFromFilters(filters); + expect(query).toBe('`CustomBoolField`=false'); + }); + it('should convert two different filters', () => { const filters = [ { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', @@ -92,22 +109,22 @@ describe('toListWorkflowQueryFromFilters', () => { }, { attribute: 'WorkflowId', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', value: 'abcd', }, ]; - const query = toListWorkflowQueryFromFilters( - combineDropdownFilters(filters), - ); - expect(query).toBe('ExecutionStatus="Running" AND WorkflowId="abcd"'); + const query = toListWorkflowQueryFromFilters(combineFilters(filters)); + expect(query).toBe('`ExecutionStatus`="Running" AND `WorkflowId`="abcd"'); }); it('should convert three different filters', () => { const filters = [ { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', @@ -115,6 +132,7 @@ describe('toListWorkflowQueryFromFilters', () => { }, { attribute: 'WorkflowId', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', @@ -122,17 +140,16 @@ describe('toListWorkflowQueryFromFilters', () => { }, { attribute: 'WorkflowType', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', value: 'cronWorkflow', }, ]; - const query = toListWorkflowQueryFromFilters( - combineDropdownFilters(filters), - ); + const query = toListWorkflowQueryFromFilters(combineFilters(filters)); expect(query).toBe( - 'ExecutionStatus="Running" AND WorkflowId="abcd" AND WorkflowType="cronWorkflow"', + '`ExecutionStatus`="Running" AND `WorkflowId`="abcd" AND `WorkflowType`="cronWorkflow"', ); }); @@ -140,24 +157,27 @@ describe('toListWorkflowQueryFromFilters', () => { const filters = [ { attribute: 'StartTime', - conditional: '=', + type: 'Datetime', + conditional: '>', operator: '', parenthesis: '', value: '2 days', }, ]; + + const supportsAdvancedVisibility = isVersionNewer('1.20', '1.19'); const query = toListWorkflowQueryFromFilters( - combineDropdownFilters(filters), - ); - expect(query).toBe( - 'StartTime BETWEEN "2019-12-30T00:00:00Z" AND "2020-01-02T00:00:00Z"', + filters, + supportsAdvancedVisibility, ); + expect(query).toBe('StartTime > "2019-12-30T00:00:00.000Z"'); }); it('should convert two filters, one as datetime filter', () => { const filters = [ { attribute: 'WorkflowType', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', @@ -165,17 +185,53 @@ describe('toListWorkflowQueryFromFilters', () => { }, { attribute: 'StartTime', - conditional: '=', + type: 'Datetime', + conditional: '>', operator: '', parenthesis: '', value: '2 days', }, ]; + const supportsAdvancedVisibility = isVersionNewer('1.20', '1.19'); const query = toListWorkflowQueryFromFilters( - combineDropdownFilters(filters), + combineFilters(filters), + supportsAdvancedVisibility, ); expect(query).toBe( - 'WorkflowType="cronWorkflow" AND StartTime BETWEEN "2019-12-30T00:00:00Z" AND "2020-01-02T00:00:00Z"', + '`WorkflowType`="cronWorkflow" AND StartTime > "2019-12-30T00:00:00.000Z"', + ); + }); + + it('should convert a KeywordList filter', () => { + const filters = [ + { + attribute: 'CustomKeywordListField', + type: 'KeywordList', + conditional: 'in', + operator: '', + parenthesis: '', + value: '("Hello", "World")', + }, + { + attribute: 'CustomKeywordListField', + type: 'KeywordList', + conditional: 'is', + operator: '', + parenthesis: '', + value: null, + }, + { + attribute: 'CustomKeywordListField', + type: 'KeywordList', + conditional: '=', + operator: '', + parenthesis: '', + value: 'Hello', + }, + ]; + const query = toListWorkflowQueryFromFilters(combineFilters(filters)); + expect(query).toBe( + '`CustomKeywordListField`in("Hello", "World") AND `CustomKeywordListField` is null AND `CustomKeywordListField`="Hello"', ); }); }); diff --git a/src/lib/utilities/query/filter-workflow-query.ts b/src/lib/utilities/query/filter-workflow-query.ts index 6120af0ed..a991d07ea 100644 --- a/src/lib/utilities/query/filter-workflow-query.ts +++ b/src/lib/utilities/query/filter-workflow-query.ts @@ -1,7 +1,14 @@ -import type { - WorkflowFilter, - WorkflowSort, -} from '$lib/models/workflow-filters'; +import { get } from 'svelte/store'; + +import type { SearchAttributeFilter } from '$lib/models/search-attribute-filters'; +import { supportsAdvancedVisibility } from '$lib/stores/advanced-visibility'; +import { + SEARCH_ATTRIBUTE_TYPE, + type SearchAttributes, + type SearchAttributeType, +} from '$lib/types/workflows'; + +import { isInConditional, isNullConditional, isStartsWith } from '../is'; import { isDuration, isDurationString, toDate, tomorrow } from '../to-duration'; export type QueryKey = @@ -10,7 +17,8 @@ export type QueryKey = | 'StartTime' | 'CloseTime' | 'ExecutionTime' - | 'ExecutionStatus'; + | 'ExecutionStatus' + | 'RunId'; type FilterValue = string | Duration; @@ -20,10 +28,11 @@ const filterKeys: Readonly> = { timeRange: 'StartTime', executionStatus: 'ExecutionStatus', closeTime: 'CloseTime', + runId: 'RunId', } as const; -const isValid = (value: unknown): boolean => { - if (value === null) return false; +const isValid = (value: unknown, conditional: string): boolean => { + if (value === null && !isNullConditional(conditional)) return false; if (value === undefined) return false; if (value === '') return false; if (typeof value === 'string' && value === 'undefined') return false; @@ -31,14 +40,44 @@ const isValid = (value: unknown): boolean => { return true; }; +const formatValue = ({ + value, + type, + conditional, +}: { + value: string; + type: SearchAttributeType; + conditional: string; +}): string | boolean => { + if (type === SEARCH_ATTRIBUTE_TYPE.BOOL) { + return value.toLowerCase() === 'true' ? true : false; + } + if ( + type === SEARCH_ATTRIBUTE_TYPE.KEYWORDLIST && + isInConditional(conditional) + ) { + return value; + } + return `"${value}"`; +}; + +const getQueryKey = (attribute: string | number) => { + const key = filterKeys[attribute] ?? attribute; + if (typeof key === 'string' && /\s/g.test(key)) { + return '`' + key + '`'; + } + return key; +}; + const toFilterQueryStatement = ( attribute: keyof SearchAttributes, + type: SearchAttributeType, value: FilterValue, conditional = '=', archived: boolean, customDate: boolean, ): string => { - const queryKey = filterKeys[attribute] ?? attribute; + const queryKey = getQueryKey(attribute); if (value === 'All') return ''; @@ -47,33 +86,51 @@ const toFilterQueryStatement = ( return `${queryKey} ${value}`; } + if (isNullConditional(conditional)) { + return `\`${queryKey}\` ${conditional} ${value}`; + } + if (isDuration(value) || isDurationString(value)) { - if (archived) { - return `${queryKey} > "${toDate(value)}"`; + if (archived || get(supportsAdvancedVisibility)) { + return `${queryKey} ${conditional} "${toDate(value)}"`; } return `${queryKey} BETWEEN "${toDate(value)}" AND "${tomorrow()}"`; } - return `${queryKey}${conditional}"${value}"`; + if (isStartsWith(conditional)) { + return `\`${queryKey}\` ${conditional} ${formatValue({ + value, + type, + conditional, + })}`; + } + + return `\`${queryKey}\`${conditional}${formatValue({ + value, + type, + conditional, + })}`; }; const toQueryStatementsFromFilters = ( - filters: WorkflowFilter[], + filters: SearchAttributeFilter[], archived: boolean, ): string[] => { return filters .map( ({ attribute, + type, value, conditional, operator, parenthesis, customDate, }) => { - if (isValid(value)) { + if (isValid(value, conditional)) { let statement = toFilterQueryStatement( attribute, + type, value, conditional, archived, @@ -95,14 +152,8 @@ const toQueryStatementsFromFilters = ( }; export const toListWorkflowQueryFromFilters = ( - filters: WorkflowFilter[] = [], - sorts: WorkflowSort[] = [], + filters: SearchAttributeFilter[] = [], archived = false, ): string => { - const sortStatement = sorts.length - ? ` order by ${sorts[0].attribute} ${sorts[0].value}` - : ''; - return ( - toQueryStatementsFromFilters(filters, archived).join('') + sortStatement - ); + return toQueryStatementsFromFilters(filters, archived).join(''); }; diff --git a/src/lib/utilities/query/is-search-attribute.test.ts b/src/lib/utilities/query/is-search-attribute.test.ts index c453fbef7..534a9fc03 100644 --- a/src/lib/utilities/query/is-search-attribute.test.ts +++ b/src/lib/utilities/query/is-search-attribute.test.ts @@ -1,5 +1,9 @@ import { writable } from 'svelte/store'; + import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import type { SearchAttributes } from '$lib/types/workflows'; + import { isSearchAttribute } from './is-search-attribute'; const store = writable({ diff --git a/src/lib/utilities/query/is-search-attribute.ts b/src/lib/utilities/query/is-search-attribute.ts index e830e6db1..9253f167e 100644 --- a/src/lib/utilities/query/is-search-attribute.ts +++ b/src/lib/utilities/query/is-search-attribute.ts @@ -1,5 +1,7 @@ import { get } from 'svelte/store'; + import { searchAttributes } from '$lib/stores/search-attributes'; + import { isString } from '../is'; export const isSearchAttribute = ( diff --git a/src/lib/utilities/query/list-workflow-query.test.ts b/src/lib/utilities/query/list-workflow-query.test.ts index e6b57033a..9d3e01548 100644 --- a/src/lib/utilities/query/list-workflow-query.test.ts +++ b/src/lib/utilities/query/list-workflow-query.test.ts @@ -1,5 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + import { isFilterKey, toListWorkflowQuery } from './list-workflow-query'; +import { isVersionNewer } from '../version-check'; describe('toListWorkflowQuery', () => { beforeEach(() => { @@ -38,18 +40,18 @@ describe('toListWorkflowQuery', () => { }); it('should convert an timeRange with a Duration as a value', () => { - const tomorrow = '2020-01-02T00:00:00Z'; - const twentyFourHoursEarlier = '2019-12-31T00:00:00Z'; - - const query = toListWorkflowQuery({ timeRange: { hours: 24 } }); + const twentyFourHoursEarlier = '2019-12-31T00:00:00.000Z'; - expect(query).toBe( - `StartTime BETWEEN "${twentyFourHoursEarlier}" AND "${tomorrow}"`, + const supportsAdvancedVisibility = isVersionNewer('1.20', '1.19'); + const query = toListWorkflowQuery( + { timeRange: { hours: 24 } }, + supportsAdvancedVisibility, ); + expect(query).toBe(`StartTime > "${twentyFourHoursEarlier}"`); }); it('should convert an timeRange with a Duration as a value when archived', () => { - const twentyFourHoursEarlier = '2019-12-31T00:00:00Z'; + const twentyFourHoursEarlier = '2019-12-31T00:00:00.000Z'; const query = toListWorkflowQuery({ timeRange: { days: 1 } }, true); diff --git a/src/lib/utilities/query/list-workflow-query.ts b/src/lib/utilities/query/list-workflow-query.ts index 2c88bf5f7..fe079f6c8 100644 --- a/src/lib/utilities/query/list-workflow-query.ts +++ b/src/lib/utilities/query/list-workflow-query.ts @@ -1,7 +1,11 @@ +import { get } from 'svelte/store'; + +import { supportsAdvancedVisibility } from '$lib/stores/advanced-visibility'; import type { - WorkflowFilter, - WorkflowSort, -} from '$lib/models/workflow-filters'; + ArchiveFilterParameters, + FilterParameters, +} from '$lib/types/workflows'; + import { isDuration, isDurationString, toDate, tomorrow } from '../to-duration'; export type QueryKey = @@ -46,15 +50,6 @@ const isValid = (value: unknown): boolean => { return true; }; -const isAdvancedValid = (value: unknown): boolean => { - if (value === null) return false; - if (value === undefined) return false; - if (value === '') return true; - if (typeof value === 'string' && value === 'undefined') return false; - - return true; -}; - export const isFilterKey = (key: unknown): key is FilterKey => { if (typeof key !== 'string') return false; @@ -75,7 +70,7 @@ const toQueryStatement = ( if (value === 'All') return ''; if (isDuration(value) || isDurationString(value)) { - if (archived) { + if (archived || get(supportsAdvancedVisibility)) { return `${queryKey} > "${toDate(value)}"`; } return `${queryKey} BETWEEN "${toDate(value)}" AND "${tomorrow()}"`; @@ -84,32 +79,6 @@ const toQueryStatement = ( return `${queryKey}="${value}"`; }; -const toFilterQueryStatement = ( - attribute: keyof SearchAttributes, - value: FilterValue, - conditional = '=', - archived: boolean, - customDate: boolean, -): string => { - const queryKey = queryKeys[attribute] ?? attribute; - - if (value === 'All') return ''; - - // Custom Dates... - if (customDate) { - return `${queryKey} ${value}`; - } - - if (isDuration(value) || isDurationString(value)) { - if (archived) { - return `${queryKey} > "${toDate(value)}"`; - } - return `${queryKey} BETWEEN "${toDate(value)}" AND "${tomorrow()}"`; - } - - return `${queryKey}${conditional}"${value}"`; -}; - const toQueryStatements = ( parameters: FilterParameters | ArchiveFilterParameters, archived: boolean, @@ -128,53 +97,3 @@ export const toListWorkflowQuery = ( ): string => { return toQueryStatements(parameters, archived).join(' and '); }; - -const toQueryStatementsFromFilters = ( - filters: WorkflowFilter[], - archived: boolean, -): string[] => { - return filters - .map( - ({ - attribute, - value, - conditional, - operator, - parenthesis, - customDate, - }) => { - if (isAdvancedValid(value)) { - let statement = toFilterQueryStatement( - attribute, - value, - conditional, - archived, - customDate, - ); - if (parenthesis === '(') { - statement = `(${statement}`; - } else if (parenthesis === ')') { - statement = `${statement})`; - } - if (operator) { - statement = `${statement} ${operator}` + ' '; - } - return statement; - } - }, - ) - .filter(Boolean); -}; - -export const toListWorkflowQueryFromFilters = ( - filters: WorkflowFilter[] = [], - sorts: WorkflowSort[] = [], - archived = false, -): string => { - const sortStatement = sorts.length - ? ` order by ${sorts[0].attribute} ${sorts[0].value}` - : ''; - return ( - toQueryStatementsFromFilters(filters, archived).join('') + sortStatement - ); -}; diff --git a/src/lib/utilities/query/search-attribute-filter.test.ts b/src/lib/utilities/query/search-attribute-filter.test.ts new file mode 100644 index 000000000..dcc0b5c4b --- /dev/null +++ b/src/lib/utilities/query/search-attribute-filter.test.ts @@ -0,0 +1,176 @@ +import { writable } from 'svelte/store'; + +import { describe, expect, it } from 'vitest'; + +import type { SearchAttributes } from '$lib/types/workflows'; + +import { + formatListFilterValue, + isBooleanFilter, + isDateTimeFilter, + isDurationFilter, + isListFilter, + isNumberFilter, + isStatusFilter, + isTextFilter, +} from './search-attribute-filter'; + +const store = writable({ + BuildIds: 'KeywordList', + CloseTime: 'Datetime', + ExecutionStatus: 'Keyword', + HistoryLength: 'Int', + StartTime: 'Datetime', + TemporalSchedulePaused: 'Bool', + WorkflowId: 'Keyword', + WorkflowType: 'Keyword', + CustomA: 'Text', + CustomB: 'Double', + CustomC: 'String', +}); + +describe('isStatusFilter', () => { + it('should return true if the attribute is ExecutionStatus', () => { + expect(isStatusFilter({ attribute: 'ExecutionStatus' })).toBe(true); + }); + + it('should return false if the attribute is not ExecutionStatus', () => { + expect(isStatusFilter({ attribute: 'WorkflowType' })).toBe(false); + }); +}); + +describe('isTextFilter', () => { + it('should return true if the type is a Keyword', () => { + expect(isTextFilter({ attribute: 'WorkflowType', type: 'Keyword' })).toBe( + true, + ); + }); + + it('should return true if the attribute is a Keyword', () => { + expect(isTextFilter({ attribute: 'WorkflowType' }, store)).toBe(true); + }); + + it('should return true if the attribute is a Text', () => { + expect(isTextFilter({ attribute: 'CustomA' }, store)).toBe(true); + }); + + it('should return true if the attribute is a String', () => { + expect(isTextFilter({ attribute: 'CustomC' }, store)).toBe(true); + }); + + it('should return false if the attribute is not a Keyword or is ExecutionStatus', () => { + expect( + isTextFilter({ attribute: 'ExecutionStatus', type: 'Keyword' }), + ).toBe(false); + expect(isTextFilter({ attribute: 'BuildIds' }, store)).toBe(false); + }); +}); + +describe('isListFilter', () => { + it('should return true if the type is a KeywordList', () => { + expect(isListFilter({ attribute: 'BuildIds', type: 'KeywordList' })).toBe( + true, + ); + }); + + it('should return true if the attribute is a KeywordList', () => { + expect(isListFilter({ attribute: 'BuildIds' }, store)).toBe(true); + }); + + it('should return false if the attribute is not a KeywordList', () => { + expect(isListFilter({ attribute: 'WorkflowType' }, store)).toBe(false); + }); +}); + +describe('isNumberFilter', () => { + it('should return true if the type is an Int', () => { + expect(isNumberFilter({ attribute: 'HistoryLength', type: 'Int' })).toBe( + true, + ); + }); + + it('should return true if the attribute is an Int', () => { + expect(isNumberFilter({ attribute: 'HistoryLength' }, store)).toBe(true); + }); + + it('should return true if the attribute is a Double', () => { + expect(isNumberFilter({ attribute: 'CustomB' }, store)).toBe(true); + }); + + it('should return false if the attribute is not an Int', () => { + expect(isNumberFilter({ attribute: 'WorkflowType' }, store)).toBe(false); + expect(isNumberFilter({ attribute: 'isStartTime' }, store)).toBe(false); + }); +}); + +describe('isDurationFilter', () => { + it('should return true if the attribute is ExecutionDuration', () => { + expect(isDurationFilter({ attribute: 'ExecutionDuration' })).toBe(true); + }); + + it('should return false if the attribute is not ExecutionDuration', () => { + expect(isDurationFilter({ attribute: 'CustomB' })).toBe(false); + }); +}); + +describe('isBooleanFilter', () => { + it('should return true if the type is a Bool', () => { + expect( + isBooleanFilter({ attribute: 'TemporalSchedulePaused', type: 'Bool' }), + ).toBe(true); + }); + + it('should return true if the attribute is a Bool', () => { + expect( + isBooleanFilter({ attribute: 'TemporalSchedulePaused' }, store), + ).toBe(true); + }); + + it('should return false if the attribute is not a Bool', () => { + expect(isBooleanFilter({ attribute: 'WorkflowType' }, store)).toBe(false); + expect(isBooleanFilter({ attribute: 'HistoryLength' }, store)).toBe(false); + }); +}); + +describe('isDateTimeFilter', () => { + it('should return true if the attribute is a Datetime', () => { + expect(isDateTimeFilter({ attribute: 'CloseTime' }, store)).toBe(true); + }); + + it('should return true if the type is a Datetime', () => { + expect(isDateTimeFilter({ attribute: 'CloseTime', type: 'Datetime' })).toBe( + true, + ); + }); + + it('should return false if the attribute is not a Datetime', () => { + expect(isDateTimeFilter({ attribute: 'WorkflowType' }, store)).toBe(false); + expect(isDateTimeFilter({ attribute: 'HistoryLength' }, store)).toBe(false); + expect( + isDateTimeFilter({ attribute: 'TemporalSchedulePaused' }, store), + ).toBe(false); + }); +}); + +describe('formatListFilterValue', () => { + it('should return an empty array if there is no value', () => { + expect(formatListFilterValue('')).toStrictEqual([]); + }); + + it('should return an array of strings if the value starts with "(" and ends with ")"', () => { + expect(formatListFilterValue('("one")')).toStrictEqual(['one']); + expect(formatListFilterValue('("one", "two")')).toStrictEqual([ + 'one', + 'two', + ]); + expect(formatListFilterValue('("one","two","three")')).toStrictEqual([ + 'one', + 'two', + 'three', + ]); + }); + + it('should return an array with the value', () => { + expect(formatListFilterValue('example')).toStrictEqual(['example']); + }); +}); diff --git a/src/lib/utilities/query/search-attribute-filter.ts b/src/lib/utilities/query/search-attribute-filter.ts new file mode 100644 index 000000000..6936771f0 --- /dev/null +++ b/src/lib/utilities/query/search-attribute-filter.ts @@ -0,0 +1,99 @@ +import { get } from 'svelte/store'; + +import type { SearchAttributeFilter } from '$lib/models/search-attribute-filters'; +import { searchAttributes } from '$lib/stores/search-attributes'; +import { SEARCH_ATTRIBUTE_TYPE } from '$lib/types/workflows'; + +export function isStatusFilter({ attribute }: SearchAttributeFilter) { + return attribute === 'ExecutionStatus'; +} + +export function isTextFilter( + filter: SearchAttributeFilter, + attributes = searchAttributes, +) { + const { attribute, type } = filter; + if (isStatusFilter(filter)) return false; + if (type === SEARCH_ATTRIBUTE_TYPE.KEYWORD) return true; + + const searchAttributeType = get(attributes)[attribute]; + return [ + SEARCH_ATTRIBUTE_TYPE.KEYWORD, + SEARCH_ATTRIBUTE_TYPE.TEXT, + 'String', + ].includes(searchAttributeType); +} + +export function isListFilter( + { attribute, type }: SearchAttributeFilter, + attributes = searchAttributes, +) { + if (type === SEARCH_ATTRIBUTE_TYPE.KEYWORDLIST) return true; + + const searchAttributeType = get(attributes)[attribute]; + return searchAttributeType === SEARCH_ATTRIBUTE_TYPE.KEYWORDLIST; +} + +export function isNumberFilter( + { attribute, type }: SearchAttributeFilter, + attributes = searchAttributes, +) { + if (type === SEARCH_ATTRIBUTE_TYPE.INT) return true; + + const searchAttributeType = get(attributes)[attribute]; + return ( + searchAttributeType === SEARCH_ATTRIBUTE_TYPE.INT || + searchAttributeType === SEARCH_ATTRIBUTE_TYPE.DOUBLE + ); +} + +export function isDurationFilter({ attribute }: SearchAttributeFilter) { + return ['ExecutionDuration'].includes(attribute); +} + +export function isDateTimeFilter( + { attribute, type }: SearchAttributeFilter, + attributes = searchAttributes, +) { + if (type === SEARCH_ATTRIBUTE_TYPE.DATETIME) return true; + + const searchAttributeType = get(attributes)[attribute]; + return searchAttributeType === SEARCH_ATTRIBUTE_TYPE.DATETIME; +} + +export function isBooleanFilter( + { attribute, type }: SearchAttributeFilter, + attributes = searchAttributes, +) { + if (type === SEARCH_ATTRIBUTE_TYPE.BOOL) return true; + + const searchAttributeType = get(attributes)[attribute]; + return searchAttributeType === SEARCH_ATTRIBUTE_TYPE.BOOL; +} + +export function getFocusedElementId(filter: SearchAttributeFilter) { + if (isStatusFilter(filter)) return 'status-filter'; + + if ( + isTextFilter(filter) || + isNumberFilter(filter) || + isDateTimeFilter(filter) || + isListFilter(filter) + ) + return 'conditional-menu-button'; + + if (isBooleanFilter(filter)) return 'boolean-filter'; + + return ''; +} + +export function formatListFilterValue(value: string): string[] { + if (value.startsWith('(') && value.endsWith(')')) { + return value + .slice(1, -1) + .split(',') + .map((v) => v.trim().slice(1, -1)); + } + if (value) return [value]; + return []; +} diff --git a/src/lib/utilities/query/to-list-workflow-filters.test.ts b/src/lib/utilities/query/to-list-workflow-filters.test.ts index b5c5e3fd1..89fc702f5 100644 --- a/src/lib/utilities/query/to-list-workflow-filters.test.ts +++ b/src/lib/utilities/query/to-list-workflow-filters.test.ts @@ -1,28 +1,39 @@ import { parseISO } from 'date-fns'; import { afterEach } from 'vitest'; import { describe, expect, it, vi } from 'vitest'; + import { - toListWorkflowFilters, combineDropdownFilters, + combineFilters, + toListWorkflowFilters, } from './to-list-workflow-filters'; -const executionStatusQuery = 'ExecutionStatus="Completed"'; +const executionStatusQuery = '`ExecutionStatus`="Completed"'; const multipleExecutionStatusQuery = - '(ExecutionStatus="Canceled" OR ExecutionStatus="Failed" OR ExecutionStatus="Completed")'; - -const workflowIdQuery = 'WorkflowId="Hello"'; -const workflowTypeQuery = 'WorkflowType="World"'; -const workflowQuery1 = 'WorkflowId="Hello" AND WorkflowType="World"'; -const startTimeQuery = - 'StartTime BETWEEN "2022-04-18T17:45:18-06:00" AND "2022-04-20T17:45:18-06:00"'; -const closeTimeQuery = - 'CloseTime BETWEEN "2022-04-18T17:45:18-06:00" AND "2022-04-20T17:45:18-06:00"'; + '(`ExecutionStatus`="Canceled" OR `ExecutionStatus`="Failed" OR `ExecutionStatus`="Completed")'; + +const workflowIdQuery = '`WorkflowId`="Hello world"'; +const workflowTypeQuery = '`WorkflowType`="World"'; +const workflowQuery1 = '`WorkflowId`="Hello world" AND `WorkflowType`="World"'; +const startTimeQuery = '`StartTime` > "2022-04-18T17:45:18-06:00"'; +const closeTimeQuery = '`CloseTime` > "2022-04-18T17:45:18-06:00"'; +const booleanQuery = '`CustomBoolField`=true'; +const betweenTimeQuery = + '`StartTime` BETWEEN "2023-07-28T00:00:00-00:00" AND "2023-07-28T06:00:00-00:00"'; const workflowQuery2 = - 'WorkflowType="World" AND StartTime BETWEEN "2022-04-18T17:45:18-06:00" AND "2022-04-20T17:45:18-06:00"'; + '`WorkflowType`="World" AND `StartTime` > "2022-04-18T17:45:18-06:00"'; const workflowQuery3 = - 'WorkflowType="World" AND StartTime BETWEEN "2022-04-18T17:45:18-06:00" AND "2022-04-20T17:45:18-06:00" AND ExecutionStatus="Canceled"'; + '`WorkflowType`="World" AND `StartTime` > "2022-04-18T17:45:18-06:00" AND `ExecutionStatus`="Canceled"'; const workflowQuery4 = - '(ExecutionStatus="Canceled" OR ExecutionStatus="Failed" OR ExecutionStatus="Completed") AND WorkflowType="World" AND StartTime BETWEEN "2022-04-18T17:45:18-06:00" AND "2022-04-20T17:45:18-06:00"'; + '(`ExecutionStatus`="Canceled" OR `ExecutionStatus`="Failed" OR `ExecutionStatus`="Completed") AND WorkflowType="World" AND StartTime > "2022-04-18T17:45:18-06:00"'; +const customAttributesWithSpacesQuery = + '`Custom Bool Field`=true AND `Custom Keyword Field`="Hello world"'; +const workflowQueryWithSpaces = + '`WorkflowId`="One and Two" AND `Custom Keyword Field`="Hello = world"'; +const prefixQuery = '`WorkflowType` STARTS_WITH "hello"'; +const isEmptyQuery = '`WorkflowType` is null'; +const isNotEmptyQuery = '`StartTime` IS NOT NULL'; +const keywordListQuery = '`CustomKeywordListField`in("Hello", "World")'; const attributes = { CloseTime: 'Datetime', @@ -30,19 +41,88 @@ const attributes = { StartTime: 'Datetime', WorkflowId: 'Keyword', WorkflowType: 'Keyword', + CustomBoolField: 'Bool', + 'Custom Keyword Field': 'Keyword', + 'Custom Bool Field': 'Bool', + CustomKeywordListField: 'KeywordList', }; -describe('toListWorkflowParameters', () => { +describe('toListWorkflowFilters', () => { afterEach(() => { vi.clearAllMocks(); vi.useRealTimers(); }); + it('should parse a query with values that have spaces', () => { + const result = toListWorkflowFilters(workflowQueryWithSpaces, attributes); + const expectedFilters = [ + { + attribute: 'WorkflowId', + type: 'Keyword', + conditional: '=', + operator: 'AND', + parenthesis: '', + value: 'One and Two', + }, + { + attribute: 'Custom Keyword Field', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'Hello = world', + }, + ]; + expect(result).toEqual(expectedFilters); + }); + + it('should parse a query with prefix search', () => { + const result = toListWorkflowFilters(prefixQuery, attributes); + const expectedFilters = [ + { + attribute: 'WorkflowType', + type: 'Keyword', + conditional: 'STARTS_WITH', + operator: '', + parenthesis: '', + value: 'hello', + }, + ]; + expect(result).toEqual(expectedFilters); + }); + + it('should parse a query with custom attributes that have spaces', () => { + const result = toListWorkflowFilters( + customAttributesWithSpacesQuery, + attributes, + ); + const expectedFilters = [ + { + attribute: 'Custom Bool Field', + type: 'Bool', + conditional: '=', + operator: 'AND', + parenthesis: '', + value: 'true', + }, + { + attribute: 'Custom Keyword Field', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'Hello world', + }, + ]; + expect(result).toEqual(expectedFilters); + }); + it('should parse a query with an executionStatus', () => { const result = toListWorkflowFilters(executionStatusQuery, attributes); const expectedFilters = [ { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', @@ -60,6 +140,7 @@ describe('toListWorkflowParameters', () => { const expectedFilters = [ { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: 'OR', parenthesis: '(', @@ -67,6 +148,7 @@ describe('toListWorkflowParameters', () => { }, { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: 'OR', parenthesis: '', @@ -74,6 +156,7 @@ describe('toListWorkflowParameters', () => { }, { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: '', parenthesis: ')', @@ -83,25 +166,27 @@ describe('toListWorkflowParameters', () => { expect(result).toEqual(expectedFilters); }); - it('should parse a query with an workflowId', () => { + it('should parse a query with a workflowId', () => { const result = toListWorkflowFilters(workflowIdQuery, attributes); const expectedFilters = [ { attribute: 'WorkflowId', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', - value: 'Hello', + value: 'Hello world', }, ]; expect(result).toEqual(expectedFilters); }); - it('should parse a query with an workflowType', () => { + it('should parse a query with a workflowType', () => { const result = toListWorkflowFilters(workflowTypeQuery, attributes); const expectedFilters = [ { attribute: 'WorkflowType', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', @@ -111,18 +196,20 @@ describe('toListWorkflowParameters', () => { expect(result).toEqual(expectedFilters); }); - it('should parse a query with an workflowId and workflowType', () => { + it('should parse a query with a workflowId and workflowType', () => { const result = toListWorkflowFilters(workflowQuery1, attributes); const expectedFilters = [ { attribute: 'WorkflowId', + type: 'Keyword', conditional: '=', operator: 'AND', parenthesis: '', - value: 'Hello', + value: 'Hello world', }, { attribute: 'WorkflowType', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', @@ -132,7 +219,7 @@ describe('toListWorkflowParameters', () => { expect(result).toEqual(expectedFilters); }); - it('should parse a query with an startTime', () => { + it('should parse a query with a startTime', () => { vi.useFakeTimers().setSystemTime( parseISO('2022-04-20T17:45:18-06:00').getTime(), ); @@ -140,16 +227,17 @@ describe('toListWorkflowParameters', () => { const expectedFilters = [ { attribute: 'StartTime', - conditional: '', + type: 'Datetime', + conditional: '>', operator: '', parenthesis: '', - value: '2 days', + value: '2022-04-18T17:45:18-06:00', }, ]; expect(result).toEqual(expectedFilters); }); - it('should parse a query with an closeTime', () => { + it('should parse a query with a closeTime', () => { vi.useFakeTimers().setSystemTime( parseISO('2022-04-20T17:45:18-06:00').getTime(), ); @@ -157,14 +245,132 @@ describe('toListWorkflowParameters', () => { const expectedFilters = [ { attribute: 'CloseTime', + type: 'Datetime', + conditional: '>', + operator: '', + parenthesis: '', + value: '2022-04-18T17:45:18-06:00', + }, + ]; + expect(result).toEqual(expectedFilters); + }); + + it('should parse a query with a Bool type', () => { + const result = toListWorkflowFilters(booleanQuery, attributes); + const expectedFilters = [ + { + attribute: 'CustomBoolField', + type: 'Bool', + conditional: '=', + operator: '', + parenthesis: '', + value: 'true', + }, + ]; + expect(result).toEqual(expectedFilters); + }); + + it('should parse a query with a KeywordList type', () => { + const result = toListWorkflowFilters(keywordListQuery, attributes); + const expectedFilters = [ + { + attribute: 'CustomKeywordListField', + type: 'KeywordList', + conditional: 'in', + operator: '', + parenthesis: '', + value: '("Hello", "World")', + }, + ]; + expect(result).toEqual(expectedFilters); + }); + + it('should parse a query with a KeywordList type and other types', () => { + const result = toListWorkflowFilters( + keywordListQuery + ' AND ' + workflowQuery4 + ' AND ' + keywordListQuery, + attributes, + ); + const expectedFilters = [ + { + attribute: 'CustomKeywordListField', + type: 'KeywordList', + conditional: 'in', + operator: 'AND', + parenthesis: '', + value: '("Hello", "World")', + }, + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: 'OR', + parenthesis: '(', + value: 'Canceled', + }, + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: 'OR', + parenthesis: '', + value: 'Failed', + }, + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: 'AND', + parenthesis: ')', + value: 'Completed', + }, + { + attribute: 'WorkflowType', + type: 'Keyword', + conditional: '=', + operator: 'AND', + parenthesis: '', + value: 'World', + }, + { + attribute: 'StartTime', + type: 'Datetime', + conditional: '>', + operator: 'AND', + parenthesis: '', + value: '2022-04-18T17:45:18-06:00', + }, + { + attribute: 'CustomKeywordListField', + type: 'KeywordList', + conditional: 'in', + operator: '', + parenthesis: '', + value: '("Hello", "World")', + }, + ]; + expect(result).toEqual(expectedFilters); + }); + + it('should parse a query BETWEEN two times', () => { + vi.useFakeTimers().setSystemTime( + parseISO('2022-04-20T17:45:18-06:00').getTime(), + ); + const result = toListWorkflowFilters(betweenTimeQuery, attributes); + const expectedFilters = [ + { + attribute: 'StartTime', + type: 'Datetime', conditional: '', operator: '', parenthesis: '', - value: '2 days', + value: + 'BETWEEN "2023-07-28T00:00:00-00:00" AND "2023-07-28T06:00:00-00:00"', + customDate: true, }, ]; expect(result).toEqual(expectedFilters); }); + it('should parse a query with a workflowType and startTime', () => { vi.useFakeTimers().setSystemTime( parseISO('2022-04-20T17:45:18-06:00').getTime(), @@ -173,6 +379,7 @@ describe('toListWorkflowParameters', () => { const expectedFilters = [ { attribute: 'WorkflowType', + type: 'Keyword', conditional: '=', operator: 'AND', parenthesis: '', @@ -180,10 +387,42 @@ describe('toListWorkflowParameters', () => { }, { attribute: 'StartTime', + type: 'Datetime', + conditional: '>', + operator: '', + parenthesis: '', + value: '2022-04-18T17:45:18-06:00', + }, + ]; + expect(result).toEqual(expectedFilters); + }); + + it('should parse a query with a workflowId and BETWEEN two times', () => { + vi.useFakeTimers().setSystemTime( + parseISO('2022-04-20T17:45:18-06:00').getTime(), + ); + const result = toListWorkflowFilters( + `${workflowIdQuery} AND ${betweenTimeQuery}`, + attributes, + ); + const expectedFilters = [ + { + attribute: 'WorkflowId', + type: 'Keyword', + conditional: '=', + operator: 'AND', + parenthesis: '', + value: 'Hello world', + }, + { + attribute: 'StartTime', + type: 'Datetime', conditional: '', operator: '', parenthesis: '', - value: '2 days', + value: + 'BETWEEN "2023-07-28T00:00:00-00:00" AND "2023-07-28T06:00:00-00:00"', + customDate: true, }, ]; expect(result).toEqual(expectedFilters); @@ -197,6 +436,7 @@ describe('toListWorkflowParameters', () => { const expectedFilters = [ { attribute: 'WorkflowType', + type: 'Keyword', conditional: '=', operator: 'AND', parenthesis: '', @@ -204,13 +444,15 @@ describe('toListWorkflowParameters', () => { }, { attribute: 'StartTime', - conditional: '', + type: 'Datetime', + conditional: '>', operator: 'AND', parenthesis: '', - value: '2 days', + value: '2022-04-18T17:45:18-06:00', }, { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', @@ -219,6 +461,7 @@ describe('toListWorkflowParameters', () => { ]; expect(result).toEqual(expectedFilters); }); + it('should parse a query with multiple executionStatuses and workflowType and startTime', () => { vi.useFakeTimers().setSystemTime( parseISO('2022-04-20T17:45:18-06:00').getTime(), @@ -228,6 +471,7 @@ describe('toListWorkflowParameters', () => { const expectedFilters = [ { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: 'OR', parenthesis: '(', @@ -235,6 +479,7 @@ describe('toListWorkflowParameters', () => { }, { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: 'OR', parenthesis: '', @@ -242,6 +487,7 @@ describe('toListWorkflowParameters', () => { }, { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: 'AND', parenthesis: ')', @@ -249,6 +495,7 @@ describe('toListWorkflowParameters', () => { }, { attribute: 'WorkflowType', + type: 'Keyword', conditional: '=', operator: 'AND', parenthesis: '', @@ -256,10 +503,11 @@ describe('toListWorkflowParameters', () => { }, { attribute: 'StartTime', - conditional: '', + type: 'Datetime', + conditional: '>', operator: '', parenthesis: '', - value: '2 days', + value: '2022-04-18T17:45:18-06:00', }, ]; expect(result).toEqual(expectedFilters); @@ -276,6 +524,40 @@ describe('toListWorkflowParameters', () => { toListWorkflowFilters('StartTime = "bogus"', attributes); expect(spy).toHaveBeenCalled(); }); + + it('should not throw if given an invalid time in a BETWEEN query', () => { + expect(() => { + toListWorkflowFilters( + 'StartTime BETWEEN "bogus" AND "2023-07-28T06:00:00-00:00"', + attributes, + ); + }).not.toThrow(); + + expect(() => { + toListWorkflowFilters( + 'StartTime BETWEEN "2023-07-28T00:00:00-00:00" AND "bogus"', + attributes, + ); + }).not.toThrow(); + }); + + it('should console error if given an invalid start time in a BETWEEN query', () => { + const spy = vi.spyOn(console, 'error'); + toListWorkflowFilters( + 'StartTime BETWEEN "bogus" AND "2023-07-28T06:00:00-00:00"', + attributes, + ); + expect(spy).toHaveBeenCalled(); + }); + + it('should console error if given an invalid end time in a BETWEEN query', () => { + const spy = vi.spyOn(console, 'error'); + toListWorkflowFilters( + 'StartTime BETWEEN "2023-07-28T00:00:00-00:00" AND "bogus"', + attributes, + ); + expect(spy).toHaveBeenCalled(); + }); }); describe('combineDropdownFilters', () => { @@ -283,6 +565,7 @@ describe('combineDropdownFilters', () => { const filters = [ { attribute: 'WorkflowType', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', @@ -290,6 +573,7 @@ describe('combineDropdownFilters', () => { }, { attribute: 'WorkflowId', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', @@ -301,6 +585,7 @@ describe('combineDropdownFilters', () => { expect(result).toEqual([ { attribute: 'WorkflowId', + type: 'Keyword', conditional: '=', operator: 'AND', parenthesis: '', @@ -308,6 +593,7 @@ describe('combineDropdownFilters', () => { }, { attribute: 'WorkflowType', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', @@ -315,10 +601,12 @@ describe('combineDropdownFilters', () => { }, ]); }); + it('should combine two filters with a datetime', () => { const filters = [ { attribute: 'WorkflowType', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', @@ -326,10 +614,11 @@ describe('combineDropdownFilters', () => { }, { attribute: 'StartTime', - conditional: '=', + type: 'Datetime', + conditional: '>', operator: '', parenthesis: '', - value: '2 days', + value: '2022-04-20T17:45:18-06:00', }, ]; @@ -337,6 +626,7 @@ describe('combineDropdownFilters', () => { expect(result).toEqual([ { attribute: 'WorkflowType', + type: 'Keyword', conditional: '=', operator: 'AND', parenthesis: '', @@ -344,10 +634,11 @@ describe('combineDropdownFilters', () => { }, { attribute: 'StartTime', - conditional: '=', + type: 'Datetime', + conditional: '>', operator: '', parenthesis: '', - value: '2 days', + value: '2022-04-20T17:45:18-06:00', }, ]); }); @@ -356,6 +647,7 @@ describe('combineDropdownFilters', () => { const filters = [ { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', @@ -363,6 +655,7 @@ describe('combineDropdownFilters', () => { }, { attribute: 'WorkflowType', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', @@ -370,6 +663,7 @@ describe('combineDropdownFilters', () => { }, { attribute: 'WorkflowId', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', @@ -381,6 +675,7 @@ describe('combineDropdownFilters', () => { expect(result).toEqual([ { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: 'AND', parenthesis: '', @@ -388,6 +683,7 @@ describe('combineDropdownFilters', () => { }, { attribute: 'WorkflowId', + type: 'Keyword', conditional: '=', operator: 'AND', parenthesis: '', @@ -395,6 +691,7 @@ describe('combineDropdownFilters', () => { }, { attribute: 'WorkflowType', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', @@ -402,10 +699,12 @@ describe('combineDropdownFilters', () => { }, ]); }); + it('should combine filters with an OR statement', () => { const filters = [ { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: 'OR', parenthesis: '(', @@ -413,6 +712,7 @@ describe('combineDropdownFilters', () => { }, { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: 'OR', parenthesis: '', @@ -420,6 +720,7 @@ describe('combineDropdownFilters', () => { }, { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: '', parenthesis: ')', @@ -427,6 +728,7 @@ describe('combineDropdownFilters', () => { }, { attribute: 'WorkflowType', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', @@ -434,6 +736,7 @@ describe('combineDropdownFilters', () => { }, { attribute: 'WorkflowId', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', @@ -445,6 +748,7 @@ describe('combineDropdownFilters', () => { expect(result).toEqual([ { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: 'OR', parenthesis: '(', @@ -452,6 +756,7 @@ describe('combineDropdownFilters', () => { }, { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: 'OR', parenthesis: '', @@ -459,6 +764,7 @@ describe('combineDropdownFilters', () => { }, { attribute: 'ExecutionStatus', + type: 'Keyword', conditional: '=', operator: 'AND', parenthesis: ')', @@ -466,6 +772,7 @@ describe('combineDropdownFilters', () => { }, { attribute: 'WorkflowId', + type: 'Keyword', conditional: '=', operator: 'AND', parenthesis: '', @@ -473,6 +780,34 @@ describe('combineDropdownFilters', () => { }, { attribute: 'WorkflowType', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'World', + }, + ]); + }); +}); + +describe('combineFilters', () => { + it('should not add an AND operator if there is no previous filter', () => { + const filters = [ + { + attribute: 'WorkflowType', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'World', + }, + ]; + + const result = combineFilters(filters); + expect(result).toEqual([ + { + attribute: 'WorkflowType', + type: 'Keyword', conditional: '=', operator: '', parenthesis: '', @@ -480,4 +815,437 @@ describe('combineDropdownFilters', () => { }, ]); }); + + it('should not add an AND operator if there is already an operator', () => { + const filters = [ + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: 'OR', + parenthesis: '', + value: 'Running', + }, + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'Canceled', + }, + ]; + + const result = combineFilters(filters); + expect(result).toEqual([ + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: 'OR', + parenthesis: '(', + value: 'Running', + }, + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: ')', + value: 'Canceled', + }, + ]); + }); + + it('should clear the filter operator if there is no following filter', () => { + const filters = [ + { + attribute: 'WorkflowType', + type: 'Keyword', + conditional: '=', + operator: 'AND', + parenthesis: '', + value: 'World', + }, + ]; + + const result = combineFilters(filters); + expect(result).toEqual([ + { + attribute: 'WorkflowType', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'World', + }, + ]); + }); + + it('should combine two filters', () => { + const filters = [ + { + attribute: 'WorkflowId', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'Hello', + }, + { + attribute: 'WorkflowType', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'World', + }, + ]; + + const result = combineFilters(filters); + expect(result).toEqual([ + { + attribute: 'WorkflowId', + type: 'Keyword', + conditional: '=', + operator: 'AND', + parenthesis: '', + value: 'Hello', + }, + { + attribute: 'WorkflowType', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'World', + }, + ]); + }); + + it('should combine three filters', () => { + const filters = [ + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'Running', + }, + { + attribute: 'WorkflowId', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'Hello', + }, + { + attribute: 'WorkflowType', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'World', + }, + ]; + + const result = combineFilters(filters); + expect(result).toEqual([ + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: 'AND', + parenthesis: '', + value: 'Running', + }, + { + attribute: 'WorkflowId', + type: 'Keyword', + conditional: '=', + operator: 'AND', + parenthesis: '', + value: 'Hello', + }, + { + attribute: 'WorkflowType', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'World', + }, + ]); + }); + + it('should combine filters with an OR statement', () => { + const filters = [ + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: 'OR', + parenthesis: '', + value: 'Running', + }, + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: 'OR', + parenthesis: '', + value: 'Canceled', + }, + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'Failed', + }, + { + attribute: 'WorkflowId', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'Hello', + }, + { + attribute: 'WorkflowType', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'World', + }, + ]; + + const result = combineFilters(filters); + expect(result).toEqual([ + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: 'OR', + parenthesis: '(', + value: 'Running', + }, + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: 'OR', + parenthesis: '', + value: 'Canceled', + }, + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: 'AND', + parenthesis: ')', + value: 'Failed', + }, + { + attribute: 'WorkflowId', + type: 'Keyword', + conditional: '=', + operator: 'AND', + parenthesis: '', + value: 'Hello', + }, + { + attribute: 'WorkflowType', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'World', + }, + ]); + }); + + it('should clear parenthesis for filters with an OR statement', () => { + const filters = [ + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: 'OR', + parenthesis: '', + value: 'Running', + }, + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: 'OR', + parenthesis: ')', + value: 'Canceled', + }, + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'Failed', + }, + ]; + + const result = combineFilters(filters); + expect(result).toEqual([ + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: 'OR', + parenthesis: '(', + value: 'Running', + }, + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: 'OR', + parenthesis: '', + value: 'Canceled', + }, + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: ')', + value: 'Failed', + }, + ]); + }); + + it('should clear parenthesis and OR statement if next filter is not the same attribute', () => { + const filters = [ + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: 'OR', + parenthesis: '(', + value: 'Running', + }, + { + attribute: 'WorkflowType', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'World', + }, + ]; + + const result = combineFilters(filters); + expect(result).toEqual([ + { + attribute: 'ExecutionStatus', + type: 'Keyword', + conditional: '=', + operator: 'AND', + parenthesis: '', + value: 'Running', + }, + { + attribute: 'WorkflowType', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'World', + }, + ]); + }); + + it('should parse a query with IS NULL', () => { + const result = toListWorkflowFilters(isEmptyQuery, attributes); + const expectedFilters = [ + { + attribute: 'WorkflowType', + type: 'Keyword', + conditional: 'is', + operator: '', + parenthesis: '', + value: null, + }, + ]; + expect(result).toEqual(expectedFilters); + }); + + it('should parse a query with IS NOT NULL', () => { + const result = toListWorkflowFilters(isNotEmptyQuery, attributes); + const expectedFilters = [ + { + attribute: 'StartTime', + type: 'Datetime', + conditional: 'IS NOT', + operator: '', + parenthesis: '', + value: null, + }, + ]; + expect(result).toEqual(expectedFilters); + }); + + it('should parse a query with IS NULL and IS NOT NULL', () => { + const result = toListWorkflowFilters( + `${isEmptyQuery} AND ${isNotEmptyQuery}`, + attributes, + ); + const expectedFilters = [ + { + attribute: 'WorkflowType', + type: 'Keyword', + conditional: 'is', + operator: 'AND', + parenthesis: '', + value: null, + }, + { + attribute: 'StartTime', + type: 'Datetime', + conditional: 'IS NOT', + operator: '', + parenthesis: '', + value: null, + }, + ]; + expect(result).toEqual(expectedFilters); + }); + + it('should parse a query with "is" and "is not" as a value', () => { + const result = toListWorkflowFilters( + '`WorkflowId`="is" AND `WorkflowType`="is not"', + attributes, + ); + const expectedFilters = [ + { + attribute: 'WorkflowId', + type: 'Keyword', + conditional: '=', + operator: 'AND', + parenthesis: '', + value: 'is', + }, + { + attribute: 'WorkflowType', + type: 'Keyword', + conditional: '=', + operator: '', + parenthesis: '', + value: 'is not', + }, + ]; + expect(result).toEqual(expectedFilters); + }); }); diff --git a/src/lib/utilities/query/to-list-workflow-filters.ts b/src/lib/utilities/query/to-list-workflow-filters.ts index 05d737118..a2ac933e3 100644 --- a/src/lib/utilities/query/to-list-workflow-filters.ts +++ b/src/lib/utilities/query/to-list-workflow-filters.ts @@ -1,17 +1,25 @@ -import type { - WorkflowFilter, - WorkflowSort, -} from '$lib/models/workflow-filters'; -import { searchAttributeOptions } from '$lib/stores/search-attributes'; -import { formatDuration } from 'date-fns'; import debounce from 'just-debounce'; + +import type { SearchAttributeFilter } from '$lib/models/search-attribute-filters'; +import { currentPageKey } from '$lib/stores/pagination'; +import { + type FilterParameters, + SEARCH_ATTRIBUTE_TYPE, + type SearchAttributes, +} from '$lib/types/workflows'; import { toListWorkflowQueryFromFilters } from '$lib/utilities/query/filter-workflow-query'; -import { isConditional, isJoin, isParenthesis, isBetween } from '../is'; -import { durationKeys, fromDate } from '../to-duration'; import { tokenize } from './tokenize'; +import { isValidDate } from '../format-date'; +import { + isBetween, + isConditional, + isJoin, + isNullConditional, + isParenthesis, +} from '../is'; +import { durationKeys } from '../to-duration'; import { updateQueryParameters } from '../update-query-parameters'; - type Tokens = string[]; export type ParsedParameters = FilterParameters & { timeRange?: string }; @@ -22,9 +30,15 @@ const is = return false; }; +const getOneAhead = (tokens: Tokens, index: number): string => + tokens[index + 1]; + const getTwoAhead = (tokens: Tokens, index: number): string => tokens[index + 2]; +const getThreeAhead = (tokens: Tokens, index: number): string => + tokens[index + 3]; + const getTwoBehind = (tokens: Tokens, index: number): string => tokens[index - 2]; @@ -37,10 +51,12 @@ export const getLargestDurationUnit = (duration: Duration): Duration => { } }; -const isDatetimeStatement = is('Datetime'); +const isDatetimeStatement = is(SEARCH_ATTRIBUTE_TYPE.DATETIME); +const isBoolStatement = is(SEARCH_ATTRIBUTE_TYPE.BOOL); -const emptyFilter = () => ({ +export const emptyFilter = (): SearchAttributeFilter => ({ attribute: '', + type: SEARCH_ATTRIBUTE_TYPE.KEYWORD, value: '', operator: '', parenthesis: '', @@ -48,19 +64,20 @@ const emptyFilter = () => ({ }); const DefaultAttributes: SearchAttributes = { - ExecutionStatus: 'Keyword', - StartTime: 'Datetime', - CloseTime: 'Datetime', - WorkflowId: 'Keyword', - WorkflowType: 'Keyword', + ExecutionStatus: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + StartTime: SEARCH_ATTRIBUTE_TYPE.DATETIME, + CloseTime: SEARCH_ATTRIBUTE_TYPE.DATETIME, + WorkflowId: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + WorkflowType: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + RunId: SEARCH_ATTRIBUTE_TYPE.KEYWORD, }; export const toListWorkflowFilters = ( query: string, attributes: SearchAttributes = DefaultAttributes, -): WorkflowFilter[] => { +): SearchAttributeFilter[] => { const tokens = tokenize(query); - const filters: WorkflowFilter[] = []; + const filters: SearchAttributeFilter[] = []; let filter = emptyFilter(); if (!query) { @@ -69,26 +86,53 @@ export const toListWorkflowFilters = ( try { tokens.forEach((token, index) => { + const nextToken = getOneAhead(tokens, index); + const tokenTwoAhead = getTwoAhead(tokens, index); + if (attributes[token]) { filter.attribute = token; - if (isDatetimeStatement(attributes[token])) { - const start = getTwoAhead(tokens, index); - - try { - const duration = fromDate(start); - const largestUnit = getLargestDurationUnit(duration); - - filter.value = formatDuration(largestUnit); - } catch (error) { - console.error('Error parsing Datetime field from query', error); + filter.type = attributes[token]; + + if (isNullConditional(nextToken)) { + const combinedTokens = `${nextToken} ${tokenTwoAhead}`; + const value = isNullConditional(combinedTokens) + ? getThreeAhead(tokens, index) + : tokenTwoAhead; + filter.value = value.toLocaleLowerCase() === 'null' ? null : value; + } else if (isDatetimeStatement(attributes[token])) { + const start = tokenTwoAhead; + const hasValidStartTime = isValidDate(start); + + if (isBetween(nextToken)) { + const end = tokens[index + 4]; + const hasValidEndTime = isValidDate(end); + + if (hasValidStartTime && hasValidEndTime) { + filter.value = `BETWEEN "${start}" AND "${end}"`; + filter.customDate = true; + } else { + console.error('Error parsing Datetime field from query'); + } + } else if (hasValidStartTime) { + filter.value = start; + } else { + console.error('Error parsing Datetime field from query'); } + } else if (isBoolStatement(filter.type)) { + filter.value = nextToken.replace('=', ''); + filter.conditional = '='; } else { - filter.value = getTwoAhead(tokens, index); + filter.value = tokenTwoAhead; } } - if (isConditional(token)) { - filter.conditional = token; + if (!filter.conditional && isConditional(token)) { + const combinedTokens = `${token} ${nextToken}`; + if (isNullConditional(combinedTokens)) { + filter.conditional = combinedTokens; + } else { + filter.conditional = token; + } } if (isParenthesis(token)) { @@ -114,7 +158,7 @@ export const toListWorkflowFilters = ( } }; -export const combineDropdownFilters = (filters: WorkflowFilter[]) => { +export const combineDropdownFilters = (filters: SearchAttributeFilter[]) => { const statusFilters = filters.filter( (f) => f.attribute === 'ExecutionStatus' && f.value, ); @@ -124,6 +168,7 @@ export const combineDropdownFilters = (filters: WorkflowFilter[]) => { const typeFilter = filters.filter( (f) => f.attribute === 'WorkflowType' && f.value, ); + const runIdFilter = filters.filter((f) => f.attribute === 'RunId' && f.value); const timeFilter = filters.filter( (f) => (f.attribute === 'StartTime' || f.attribute === 'CloseTime') && f.value, @@ -132,6 +177,7 @@ export const combineDropdownFilters = (filters: WorkflowFilter[]) => { const activeFilters = [ statusFilters, idFilter, + runIdFilter, typeFilter, timeFilter, ].filter((f) => f.length); @@ -144,38 +190,57 @@ export const combineDropdownFilters = (filters: WorkflowFilter[]) => { } }); - return [...statusFilters, ...idFilter, ...typeFilter, ...timeFilter]; + return [ + ...statusFilters, + ...idFilter, + ...runIdFilter, + ...typeFilter, + ...timeFilter, + ]; +}; + +export const combineFilters = (filters: SearchAttributeFilter[]) => { + filters.forEach((filter, index) => { + const previousFilter = filters[index - 1]; + if (previousFilter && !previousFilter.operator) { + previousFilter.operator = 'AND'; + } + + const nextFilter = filters[index + 1]; + if (!nextFilter) { + filter.operator = ''; + } + + if ( + filter.operator === 'OR' && + nextFilter?.attribute !== filter.attribute + ) { + filter.parenthesis = ''; + filter.operator = ''; + } else if (filter.operator === 'OR' && previousFilter?.operator !== 'OR') { + filter.parenthesis = '('; + } else if (previousFilter?.operator === 'OR' && filter.operator !== 'OR') { + filter.parenthesis = ')'; + } else { + filter.parenthesis = ''; + } + }); + return filters; }; export const updateQueryParamsFromFilter = debounce( - (url: URL, filters: WorkflowFilter[], sorts: WorkflowSort[]) => { - const allFilters = combineDropdownFilters(filters); - const query = toListWorkflowQueryFromFilters(allFilters, sorts); + (url: URL, filters: SearchAttributeFilter[], isDropdownFilter = false) => { + const allFilters = isDropdownFilter + ? combineDropdownFilters(filters) + : combineFilters(filters); + const query = toListWorkflowQueryFromFilters(allFilters); updateQueryParameters({ url, parameter: 'query', value: query, allowEmpty: false, + clearParameters: [currentPageKey], }); }, 300, ); - -export const getConditionalForAttribute = ( - attribute: keyof SearchAttributes, -): string => { - const filter = searchAttributeOptions().find((t) => t.value === attribute); - const type = filter?.type; - if (type === 'Datetime') return 'In Last'; - return '='; -}; - -export const getDefaultValueForAttribute = ( - attribute: keyof SearchAttributes, -) => { - const filter = searchAttributeOptions().find((t) => t.value === attribute); - const type = filter?.type; - if (type === 'Datetime') return '24 hours'; - if (type === 'Bool') return 'true'; - return ''; -}; diff --git a/src/lib/utilities/query/to-list-workflow-parameters.test.ts b/src/lib/utilities/query/to-list-workflow-parameters.test.ts index e5b1d5a3a..20f089d75 100644 --- a/src/lib/utilities/query/to-list-workflow-parameters.test.ts +++ b/src/lib/utilities/query/to-list-workflow-parameters.test.ts @@ -1,6 +1,7 @@ import { parseISO } from 'date-fns'; import { afterEach } from 'vitest'; import { describe, expect, it, vi } from 'vitest'; + import { getLargestDurationUnit, toListWorkflowParameters, @@ -10,8 +11,7 @@ const executionStatusQuery = 'ExecutionStatus="Completed"'; const workflowIdQuery = 'WorkflowId="Hello"'; const workflowTypeQuery = 'WorkflowType="World"'; const workflowQuery = 'WorkflowId="Hello" and WorkflowType="World"'; -const startTimeQuery = - 'StartTime BETWEEN "2022-04-18T17:45:18-06:00" AND "2022-04-20T17:45:18-06:00"'; +const startTimeQuery = 'StartTime > "2022-04-18T17:45:18-06:00"'; const defaultParameters = { workflowId: '', diff --git a/src/lib/utilities/query/to-list-workflow-parameters.ts b/src/lib/utilities/query/to-list-workflow-parameters.ts index 061ef1a76..8c4bc44a9 100644 --- a/src/lib/utilities/query/to-list-workflow-parameters.ts +++ b/src/lib/utilities/query/to-list-workflow-parameters.ts @@ -1,7 +1,10 @@ import { formatDuration } from 'date-fns'; + +import type { FilterParameters } from '$lib/types/workflows'; + +import { tokenize } from './tokenize'; import { isExecutionStatus } from '../is'; import { durationKeys, fromDate } from '../to-duration'; -import { tokenize } from './tokenize'; type Tokens = string[]; export type ParsedParameters = FilterParameters & { timeRange?: string }; diff --git a/src/lib/utilities/query/tokenize.test.ts b/src/lib/utilities/query/tokenize.test.ts index 4d440903f..b32d5a358 100644 --- a/src/lib/utilities/query/tokenize.test.ts +++ b/src/lib/utilities/query/tokenize.test.ts @@ -1,12 +1,18 @@ import { describe, expect, it } from 'vitest'; + import { tokenize } from './tokenize'; -const executionStatusQuery = 'ExecutionStatus="Completed"'; -const startTimeQuery = - 'StartTime BETWEEN "2022-04-18T17:45:18-06:00" AND "2022-04-20T17:45:18-06:00"'; -const workflowQuery = 'WorkflowId="Hello" and WorkflowType="World"'; +const executionStatusQuery = '`ExecutionStatus`="Completed"'; +const startTimeQuery = '`StartTime` > "2022-04-18T17:45:18-06:00"'; +const workflowQuery = '`WorkflowId`="Hello" and `WorkflowType`="World"'; +const customAttributesWithSpacesQuery = + '(`ExecutionStatus`="Running" OR `ExecutionStatus`="TimedOut") AND `Custom Key Word`="Hello there" AND `WorkflowId`="some workflow" AND `Custom Boolean`=true'; const combinedQuery = - 'WorkflowId="Hello" and WorkflowType="World" and StartTime BETWEEN "2022-04-18T18:09:49-06:00" AND "2022-04-20T18:09:49-06:00"'; + '`WorkflowId`="Hello" and `WorkflowType`="World" and `StartTime` BETWEEN "2022-04-18T18:09:49-06:00" AND "2022-04-20T18:09:49-06:00"'; +const valuesWithSpacesQuery = + '`Custom Key Word`="Hello there world" AND `WorkflowId`="one and two = three" OR `WorkflowType`="example=\'one\'"'; +const keywordListQuery = '`CustomKeywordListField`in("Hello", "World")'; +const startsWithQuery = '`WorkflowType` STARTS_WITH "Hello"'; describe('tokenize', () => { it('should eliminate spaces', () => { @@ -38,10 +44,8 @@ describe('tokenize', () => { expect(tokenize(query)).toEqual([ 'StartTime', - 'BETWEEN', + '>', '2022-04-18T17:45:18-06:00', - 'AND', - '2022-04-20T17:45:18-06:00', ]); }); @@ -59,6 +63,70 @@ describe('tokenize', () => { ]); }); + it('should tokenize the customAttributesWithSpacesQuery', () => { + const query = customAttributesWithSpacesQuery; + + expect(tokenize(query)).toEqual([ + '(', + 'ExecutionStatus', + '=', + 'Running', + 'OR', + 'ExecutionStatus', + '=', + 'TimedOut', + ')', + 'AND', + 'Custom Key Word', + '=', + 'Hello there', + 'AND', + 'WorkflowId', + '=', + 'some workflow', + 'AND', + 'Custom Boolean', + '=true', + ]); + }); + + it('should tokenize the keywordListQuery', () => { + const query = keywordListQuery; + + expect(tokenize(query)).toEqual([ + 'CustomKeywordListField', + 'in', + '("Hello", "World")', + ]); + }); + + it('should tokenize with "`" not used in custom attributes with spaces', () => { + expect(tokenize('one = "one `"')).toEqual(['one', '=', 'one', '`']); + expect(tokenize('one = "one `1`"')).toEqual(['one', '=', 'one', '`1`']); + expect(tokenize('one = "`one"')).toEqual(['one', '=', '`one']); + expect(tokenize('one = 1 `')).toEqual(['one', '=', '1', '`']); + expect(tokenize('`one = 1')).toEqual(['`one', '=', '1']); + expect(tokenize('one = `1')).toEqual(['one', '=', '`1']); + }); + + it('should tokenize the valueWithSpacesQuery', () => { + const query = valuesWithSpacesQuery; + + expect(tokenize(query)).toEqual([ + 'Custom Key Word', + '=', + 'Hello there world', + 'AND', + 'WorkflowId', + '=', + 'one and two = three', + 'OR', + 'WorkflowType', + '=', + "example='one'", + ]); + }); + it('should tokenize the combinedQuery', () => { const query = combinedQuery; @@ -78,4 +146,28 @@ describe('tokenize', () => { '2022-04-20T18:09:49-06:00', ]); }); + + it('should tokenize the startsWithQuery', () => { + const query = startsWithQuery; + + expect(tokenize(query)).toEqual(['WorkflowType', 'STARTS_WITH', 'Hello']); + }); + + it('should tokenize the startsWithInQuery for values with IN conditional in them', () => { + const query = '`WorkflowType` STARTS_WITH "Inspect"'; + + expect(tokenize(query)).toEqual(['WorkflowType', 'STARTS_WITH', 'Inspect']); + }); + + it('should tokenize the startsWithInQuery for values with IS conditional in them', () => { + const query = '`WorkflowType` STARTS_WITH "issue"'; + + expect(tokenize(query)).toEqual(['WorkflowType', 'STARTS_WITH', 'issue']); + }); + + it('should tokenize the startsWithInQuery for values with JOIN conditional in them', () => { + const query = '`WorkflowType` STARTS_WITH "Joiner"'; + + expect(tokenize(query)).toEqual(['WorkflowType', 'STARTS_WITH', 'Joiner']); + }); }); diff --git a/src/lib/utilities/query/tokenize.ts b/src/lib/utilities/query/tokenize.ts index 2004b470e..ddb6ad4a2 100644 --- a/src/lib/utilities/query/tokenize.ts +++ b/src/lib/utilities/query/tokenize.ts @@ -1,4 +1,13 @@ -import { isConditional, isParenthesis, isQuote, isSpace } from '../is'; +import { + isBacktick, + isConditional, + isEndParenthesis, + isInConditional, + isOperator, + isParenthesis, + isQuote, + isSpace, +} from '../is'; type Tokens = string[]; @@ -14,41 +23,96 @@ export const tokenize = (string: string): Tokens => { let buffer = ''; let cursor = 0; - while (cursor < string.length) { - const character = string[cursor]; + const getTokenWithSpaces = ( + breakCondition: (value: unknown) => boolean, + inclusive = false, + ) => { + for (let i = inclusive ? cursor : cursor + 1; i < string.length; i++) { + const character = string[i]; - if (isParenthesis(character)) { + if (breakCondition(character)) { + if (inclusive) buffer += character; + addBufferToTokens(); + cursor = i + 1; + return; + } buffer += character; - addBufferToTokens(); - cursor++; - continue; } + cursor++; + }; - if (isConditional(character)) { - // Conditional can be up to three characters long (!==) - const midConditional = `${string[cursor]}${string[cursor + 1]}`; - const maxConditional = `${string[cursor]}${string[cursor + 1]}${ - string[cursor + 2] - }`; - if (isConditional(maxConditional)) { + while (cursor < string.length) { + const character = string[cursor]; + + if (isBacktick(character)) { + const isPotentialStartofAttribute = + cursor === 0 || + (isSpace(string[cursor - 1]) && + isOperator(tokens[tokens.length - 1])) || + isParenthesis(string[cursor - 1]); + const hasClosingBacktick = string.slice(cursor + 1).includes(character); + + if (isPotentialStartofAttribute && hasClosingBacktick) { addBufferToTokens(); - buffer += maxConditional; - cursor += 3; + getTokenWithSpaces(isBacktick); continue; - } else if (isConditional(midConditional)) { - addBufferToTokens(); - buffer += midConditional; - cursor += 2; + } + } + + if (isParenthesis(character)) { + const prevToken = tokens[tokens.length - 1]; + if (isInConditional(prevToken)) { + getTokenWithSpaces(isEndParenthesis, true); continue; } else { - addBufferToTokens(); buffer += character; + addBufferToTokens(); cursor++; continue; } } - if (isSpace(character) || isQuote(character)) { + // Conditional can be up to three characters long (!==) + const midConditional = `${string[cursor]}${string[cursor + 1]}`; + const maxConditional = `${string[cursor]}${string[cursor + 1]}${ + string[cursor + 2] + }`; + if (isConditional(maxConditional)) { + buffer += maxConditional; + addBufferToTokens(); + cursor += 3; + continue; + } else if ( + isConditional(midConditional) && + (isSpace(string[cursor + 2]) || isParenthesis(string[cursor + 2])) + ) { + // To prevent false positives like "inspect" being a "in" conditional, check for space or parenthesis after the midConditional + buffer += midConditional; + addBufferToTokens(); + cursor += 2; + continue; + } else if (isConditional(character)) { + addBufferToTokens(); + buffer += character; + cursor++; + continue; + } + + if (isQuote(character)) { + addBufferToTokens(); + + const isPotentialStartOfValue = isConditional(string[cursor - 1]); + const hasClosingQuote = string.slice(cursor + 1).includes(character); + if (isPotentialStartOfValue && hasClosingQuote) { + const isClosingQuote = (value: unknown) => value === character; + getTokenWithSpaces(isClosingQuote); + continue; + } + cursor++; + continue; + } + + if (isSpace(character)) { addBufferToTokens(); cursor++; continue; diff --git a/src/lib/utilities/refresh-rate.test.ts b/src/lib/utilities/refresh-rate.test.ts new file mode 100644 index 000000000..513390ec5 --- /dev/null +++ b/src/lib/utilities/refresh-rate.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest'; + +import { getExponentialBackoffSeconds } from './refresh-rate'; + +describe('getExponentialBackoffSeconds', () => { + it('should get initial backoff interval of 5 seconds', () => { + expect(getExponentialBackoffSeconds(5, 1, 100)).toEqual(5); + }); + + it('should get backoff interval of 6 seconds on attempt 2', () => { + expect(getExponentialBackoffSeconds(5, 2, 100)).toEqual(6); + }); + + it('should get backoff interval of 6 seconds on attempt 3', () => { + expect(getExponentialBackoffSeconds(5, 3, 100)).toEqual(6); + }); + + it('should get backoff interval of 10 seconds on attempt 10', () => { + expect(getExponentialBackoffSeconds(5, 10, 100)).toEqual(10); + }); + + it('should get backoff interval of 19 seconds on attempt 20', () => { + expect(getExponentialBackoffSeconds(5, 20, 100)).toEqual(19); + }); + + it('should get backoff interval of 134 seconds on attempt 50', () => { + expect(getExponentialBackoffSeconds(5, 50, 100)).toEqual(134); + }); + + it('should get max backoff interval of one hour on attempt 100', () => { + expect(getExponentialBackoffSeconds(5, 100, 100)).toEqual(3600); + }); + + it('should still get max backoff interval of one hour on attempt 1000', () => { + expect(getExponentialBackoffSeconds(5, 1000, 100)).toEqual(3600); + }); +}); diff --git a/src/lib/utilities/refresh-rate.ts b/src/lib/utilities/refresh-rate.ts new file mode 100644 index 000000000..ed96276c7 --- /dev/null +++ b/src/lib/utilities/refresh-rate.ts @@ -0,0 +1,18 @@ +export const getExponentialBackoffSeconds = ( + initialIntervalSeconds: number, + attempt: number, + maxAttempts: number, +): number => { + const maxIntervalSeconds = 3600; + const growthFactor = Math.pow( + maxIntervalSeconds / initialIntervalSeconds, + 1 / maxAttempts, + ); + const exponentialBackoff = + initialIntervalSeconds * Math.pow(growthFactor, attempt); + const intervalSeconds = Math.min( + maxIntervalSeconds, + Math.round(exponentialBackoff), + ); + return intervalSeconds; +}; diff --git a/src/lib/utilities/render-markdown.ts b/src/lib/utilities/render-markdown.ts new file mode 100644 index 000000000..ad1b30078 --- /dev/null +++ b/src/lib/utilities/render-markdown.ts @@ -0,0 +1,36 @@ +import { sanitize } from 'hast-util-sanitize'; +import { toHtml } from 'hast-util-to-html'; +import { fromMarkdown } from 'mdast-util-from-markdown'; +import { toHast } from 'mdast-util-to-hast'; +import { remove } from 'unist-util-remove'; +import { visit } from 'unist-util-visit'; + +/** + * Generate an HTML page from the given markdown content. + * @param markdown The markdown content to render. + * @returns The rendered HTML. + */ +export const process = async (markdown: string) => { + const ast = fromMarkdown(markdown); + + remove(ast, 'image'); + remove(ast, 'html'); + remove(ast, 'script'); + + const hast = toHast(ast); + + visit(hast, (node) => { + if (node.type !== 'element') return; + + if (node.tagName === 'a') { + node.properties.target = '_blank'; + node.properties.rel = 'noopener noreferrer nofollow'; + } + }); + + return sanitize(hast); +}; + +export const render = async (hast: ReturnType) => { + return toHtml(hast); +}; diff --git a/src/lib/utilities/request-from-api.test.ts b/src/lib/utilities/request-from-api.test.ts index 28c12f489..3f7c3fcf4 100644 --- a/src/lib/utilities/request-from-api.test.ts +++ b/src/lib/utilities/request-from-api.test.ts @@ -1,7 +1,10 @@ import { URLSearchParams } from 'url'; + import { describe, expect, it, vi } from 'vitest'; -import { isTemporalAPIError, requestFromAPI } from './request-from-api'; + import { handleError } from './handle-error'; +import { isTemporalAPIError, requestFromAPI } from './request-from-api'; +import { routeForApi } from './route-for-api'; import listWorkflowResponse from '$fixtures/list-workflows.json'; @@ -59,7 +62,9 @@ describe('requestFromAPI', () => { const options = { credentials: 'include', - headers: {}, + headers: { + 'Caller-Type': 'operator', + }, }; const fetchMock = ( @@ -100,6 +105,7 @@ describe('requestFromAPI', () => { ...options, headers: { 'X-CSRF-TOKEN': token, + 'Caller-Type': 'operator', }, }); }); @@ -163,6 +169,29 @@ describe('requestFromAPI', () => { ); }); + it('should pass through runId query params for workflow route', async () => { + const params = { 'execution.runId': 'run123' }; + const encodedParams = new URLSearchParams(params).toString(); + const request = fetchMock(); + + const parameters = { + namespace: 'namespace', + workflowId: 'workflow', + }; + + const endpoint = routeForApi('workflow', parameters); + + await requestFromAPI(endpoint, { + request, + params, + }); + + expect(request).toHaveBeenCalledWith( + endpoint + '?' + encodedParams, + options, + ); + }); + it('should add the next page token', async () => { const params = { query: 'WorkflowId="Test"' }; const encodedParams = new URLSearchParams({ @@ -238,67 +267,4 @@ describe('requestFromAPI', () => { expect(handleError).toHaveBeenCalled(); }); - - it('should not retry if the shouldRetry is false', async () => { - const status = 403; - const statusText = 'Unauthorized'; - const body = { error: statusText }; - const ok = false; - - const onRetry = vi.fn(); - - const request = fetchMock(body, { status, ok, statusText }); - await requestFromAPI(endpoint, { - request, - shouldRetry: false, - onRetry, - }); - - expect(onRetry).not.toHaveBeenCalled(); - }); - - it('should retry if shouldRetry is true and there are retries left', async () => { - const status = 403; - const statusText = 'Unauthorized'; - const body = { error: statusText }; - const ok = false; - - const onRetry = vi.fn(); - - const request = fetchMock(body, { status, ok, statusText }); - await requestFromAPI( - endpoint, - { - request, - shouldRetry: true, - retryInterval: 0, - onRetry, - }, - 2, - ); - - expect(onRetry).toHaveBeenCalled(); - }); - - it('should not retry if there are no retries left', async () => { - const status = 403; - const statusText = 'Unauthorized'; - const body = { error: statusText }; - const ok = false; - - const onRetry = vi.fn(); - - const request = fetchMock(body, { status, ok, statusText }); - await requestFromAPI( - endpoint, - { - request, - shouldRetry: true, - onRetry, - }, - 0, - ); - - expect(onRetry).not.toHaveBeenCalled(); - }); }); diff --git a/src/lib/utilities/request-from-api.ts b/src/lib/utilities/request-from-api.ts index 9bc4fde16..36661fa17 100644 --- a/src/lib/utilities/request-from-api.ts +++ b/src/lib/utilities/request-from-api.ts @@ -1,6 +1,8 @@ -import { browser } from '$app/env'; +import { BROWSER } from 'esm-env'; + import { getAuthUser } from '$lib/stores/auth-user'; -import { noop } from 'svelte/internal'; +import type { NetworkError } from '$lib/types/global'; + import { handleError as handleRequestError } from './handle-error'; import { isFunction } from './is-function'; import { toURL } from './to-url'; @@ -16,6 +18,7 @@ export type RetryCallback = (retriesRemaining: number) => void; export type APIErrorResponse = { status: number; statusText: string; + statusCode?: number; body: TemporalAPIError; }; @@ -28,13 +31,11 @@ type RequestFromAPIOptions = { request?: typeof fetch; options?: Parameters[1]; token?: string; - onRetry?: RetryCallback; onError?: ErrorCallback; notifyOnError?: boolean; handleError?: typeof handleRequestError; - shouldRetry?: boolean; - retryInterval?: number; isBrowser?: boolean; + signal?: AbortController['signal']; }; export const isTemporalAPIError = (obj: unknown): obj is TemporalAPIError => @@ -56,32 +57,36 @@ export const isTemporalAPIError = (obj: unknown): obj is TemporalAPIError => export const requestFromAPI = async ( endpoint: toURLParams[0], init: RequestFromAPIOptions = {}, - retryCount = 10, ): Promise => { const { params = {}, request = fetch, token, - shouldRetry = false, notifyOnError = true, handleError = handleRequestError, - onRetry = noop, onError, - retryInterval = 5000, - isBrowser = browser, + isBrowser = BROWSER, } = init; let { options } = init; - const nextPageToken = token ? { next_page_token: token } : {}; - const query = new URLSearchParams({ - ...params, - ...nextPageToken, - }); + let query = new URLSearchParams(); + if (params?.entries) { + query = params as URLSearchParams; + if (token) query.set('nextPageToken', token); + } else { + const nextPageToken = token ? { next_page_token: token } : {}; + query = new URLSearchParams({ + ...params, + ...nextPageToken, + }); + } const url = toURL(endpoint, query); try { options = withSecurityOptions(options, isBrowser); - options = await withAuth(options, isBrowser); + if (!endpoint.endsWith('api/v1/settings')) { + options = await withAuth(options, isBrowser); + } const response = await request(url, options); const body = await response.json(); @@ -105,16 +110,6 @@ export const requestFromAPI = async ( } catch (error: unknown) { if (notifyOnError) { handleError(error); - - if (shouldRetry && retryCount > 0) { - return new Promise((resolve) => { - const retriesRemaining = retryCount - 1; - onRetry(retriesRemaining); - setTimeout(() => { - resolve(requestFromAPI(endpoint, init, retriesRemaining)); - }, retryInterval); - }); - } } else { throw error; } @@ -123,7 +118,7 @@ export const requestFromAPI = async ( const withSecurityOptions = ( options: RequestInit, - isBrowser = browser, + isBrowser = BROWSER, ): RequestInit => { const opts: RequestInit = { credentials: 'include', ...options }; opts.headers = withCsrf(options?.headers, isBrowser); @@ -132,23 +127,23 @@ const withSecurityOptions = ( const withAuth = async ( options: RequestInit, - isBrowser = browser, + isBrowser = BROWSER, ): Promise => { - if (getAuthUser().accessToken) { + if (globalThis?.AccessToken) { options.headers = await withBearerToken( options?.headers, - async () => getAuthUser().accessToken, + globalThis.AccessToken, isBrowser, ); - options.headers = withIdToken( + } else if (getAuthUser().accessToken) { + options.headers = await withBearerToken( options?.headers, - getAuthUser().idToken, + async () => getAuthUser().accessToken, isBrowser, ); - } else if (globalThis?.AccessToken) { - options.headers = await withBearerToken( + options.headers = withIdToken( options?.headers, - globalThis.AccessToken, + getAuthUser().idToken, isBrowser, ); } @@ -159,7 +154,7 @@ const withAuth = async ( const withBearerToken = async ( headers: HeadersInit, accessToken: () => Promise, - isBrowser = browser, + isBrowser = BROWSER, ): Promise => { // At this point in the code path, headers will always be set. /* c8 ignore next */ @@ -182,7 +177,7 @@ const withBearerToken = async ( const withIdToken = ( headers: HeadersInit = {}, idToken: string, - isBrowser = browser, + isBrowser = BROWSER, ): HeadersInit => { if (!isBrowser) return headers; @@ -193,8 +188,9 @@ const withIdToken = ( return headers; }; -const withCsrf = (headers: HeadersInit, isBrowser = browser): HeadersInit => { +const withCsrf = (headers: HeadersInit, isBrowser = BROWSER): HeadersInit => { if (!headers) headers = {}; + headers['Caller-Type'] = 'operator'; if (!isBrowser) return headers; const csrfCookie = '_csrf='; diff --git a/src/lib/utilities/request-from-api.with-access-token.test.ts b/src/lib/utilities/request-from-api.with-access-token.test.ts index 8ebf28edf..8c8dc7310 100644 --- a/src/lib/utilities/request-from-api.with-access-token.test.ts +++ b/src/lib/utilities/request-from-api.with-access-token.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it, vi } from 'vitest'; + import { requestFromAPI } from './request-from-api'; import listWorkflowResponse from '$fixtures/list-workflows.json'; @@ -12,7 +13,9 @@ type MockResponse = { const options = { credentials: 'include', - headers: {}, + headers: { + 'Caller-Type': 'operator', + }, }; const endpoint = '/api/endpoint'; diff --git a/src/lib/utilities/route-for-api.test.ts b/src/lib/utilities/route-for-api.test.ts index 60a8fcff2..dcad75caf 100644 --- a/src/lib/utilities/route-for-api.test.ts +++ b/src/lib/utilities/route-for-api.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { routeForApi } from './route-for-api'; const parameters = { @@ -9,70 +10,183 @@ const parameters = { }; describe('routeForApi', () => { - it('should return a route for workflow', async () => { - const route = await routeForApi('workflow', parameters); + it('should return a route for workflow', () => { + const route = routeForApi('workflow', parameters); expect(route).toBe( - 'http://localhost:8080/api/v1/namespaces/namespace/workflows/workflow/runs/run', + 'http://localhost:8233/api/v1/namespaces/namespace/workflows/workflow', ); }); - it('should return a route for events', async () => { - const route = await routeForApi('events.ascending', parameters); + it('should return a route for events', () => { + const route = routeForApi('events.ascending', parameters); expect(route).toBe( - 'http://localhost:8080/api/v1/namespaces/namespace/workflows/workflow/runs/run/events', + 'http://localhost:8233/api/v1/namespaces/namespace/workflows/workflow/history', ); }); - it('should return a route for events', async () => { - const route = await routeForApi('events.descending', parameters); + it('should return a route for events', () => { + const route = routeForApi('events.descending', parameters); expect(route).toBe( - 'http://localhost:8080/api/v1/namespaces/namespace/workflows/workflow/runs/run/events/reverse', + 'http://localhost:8233/api/v1/namespaces/namespace/workflows/workflow/history-reverse', ); }); - it('should return a route for task-queue', async () => { - const route = await routeForApi('task-queue', parameters); + it('should return a route for task-queue', () => { + const route = routeForApi('task-queue', parameters); expect(route).toBe( - 'http://localhost:8080/api/v1/namespaces/namespace/task-queues/queue', + 'http://localhost:8233/api/v1/namespaces/namespace/task-queues/queue', ); }); - it('should return a route for cluster', async () => { - const route = await routeForApi('cluster'); - expect(route).toBe('http://localhost:8080/api/v1/cluster'); + it('should return a route for cluster', () => { + const route = routeForApi('cluster'); + expect(route).toBe('http://localhost:8233/api/v1/cluster-info'); }); - it('should return a route for settings', async () => { - const route = await routeForApi('settings'); - expect(route).toBe('http://localhost:8080/api/v1/settings'); + it('should return a route for settings', () => { + const route = routeForApi('settings'); + expect(route).toBe('http://localhost:8233/api/v1/settings'); }); - it('should return a route for user', async () => { - const route = await routeForApi('user'); - expect(route).toBe('http://localhost:8080/api/v1/me'); + it('should return a route for user', () => { + const route = routeForApi('user'); + expect(route).toBe('http://localhost:8233/api/v1/me'); }); - it('should return a route for workflow.terminate', async () => { - const route = await routeForApi('workflow.terminate', parameters); + it('should return a route for workflow', () => { + const parameters = { + namespace: 'namespace', + workflowId: 'workflow', + }; + + const route = routeForApi('workflow', parameters); + expect(route).toBe( + 'http://localhost:8233/api/v1/namespaces/namespace/workflows/workflow', + ); + }); + + it('should return a route for workflow without a runId even if passed as a parameter', () => { + const parameters = { + namespace: 'namespace', + workflowId: 'workflow', + runId: 'run', + }; + + const route = routeForApi('workflow', parameters); + expect(route).toBe( + 'http://localhost:8233/api/v1/namespaces/namespace/workflows/workflow', + ); + }); + + it('should return a route for workflow without a runId even if passed as a parameter', () => { + const parameters = { + namespace: 'namespace', + workflowId: 'workflow', + runId: 'run', + }; + + const route = routeForApi('workflow', parameters); + expect(route).toBe( + 'http://localhost:8233/api/v1/namespaces/namespace/workflows/workflow', + ); + }); + + it('should return a route for workflow.terminate', () => { + const route = routeForApi('workflow.terminate', parameters); + expect(route).toBe( + 'http://localhost:8233/api/v1/namespaces/namespace/workflows/workflow/terminate', + ); + }); + + it('should return a route for workflow.cancel', () => { + const route = routeForApi('workflow.cancel', parameters); + expect(route).toBe( + 'http://localhost:8233/api/v1/namespaces/namespace/workflows/workflow/cancel', + ); + }); + + it('should return a route for workflow.reset', () => { + const route = routeForApi('workflow.reset', parameters); + expect(route).toBe( + 'http://localhost:8233/api/v1/namespaces/namespace/workflows/workflow/reset', + ); + }); + + it('should return a route for workflow.signal', () => { + const parameters = { + namespace: 'namespace', + workflowId: 'workflow', + runId: 'run', + signalName: 'signalName', + }; + + const route = routeForApi('workflow.signal', parameters); + expect(route).toBe( + 'http://localhost:8233/api/v1/namespaces/namespace/workflows/workflow/signal/signalName', + ); + }); + + it('should return a route for list of schedules', () => { + const parameters = { + namespace: 'namespace', + }; + + const route = routeForApi('schedules', parameters); + expect(route).toBe( + 'http://localhost:8233/api/v1/namespaces/namespace/schedules', + ); + }); + + it('should return a route for a schedule', () => { + const parameters = { + namespace: 'namespace', + scheduleId: 'scheduleName', + }; + + const route = routeForApi('schedule', parameters); + expect(route).toBe( + 'http://localhost:8233/api/v1/namespaces/namespace/schedules/scheduleName', + ); + }); + + it('should return a route for editing schedule', () => { + const parameters = { + namespace: 'namespace', + scheduleId: 'scheduleName', + }; + + const route = routeForApi('schedule.edit', parameters); + expect(route).toBe( + 'http://localhost:8233/api/v1/namespaces/namespace/schedules/scheduleName/update', + ); + }); + + it('should return a route for patching schedule', () => { + const parameters = { + namespace: 'namespace', + scheduleId: 'scheduleName', + }; + + const route = routeForApi('schedule.patch', parameters); expect(route).toBe( - 'http://localhost:8080/api/v1/namespaces/namespace/workflows/workflow/runs/run/terminate', + 'http://localhost:8233/api/v1/namespaces/namespace/schedules/scheduleName/patch', ); }); }); describe('API Request Encoding', () => { - it('should return a route for workflow', async () => { - const route = await routeForApi('workflow', { + it('should return a route for workflow', () => { + const route = routeForApi('workflow', { ...parameters, workflowId: 'workflow#with#hashes', }); expect(route).toBe( - 'http://localhost:8080/api/v1/namespaces/namespace/workflows/workflow%2523with%2523hashes/runs/run', + 'http://localhost:8233/api/v1/namespaces/namespace/workflows/workflow%23with%23hashes', ); }); - it('should handle slashes', async () => { - const route = await routeForApi('workflow', { + it('should handle slashes', () => { + const route = routeForApi('workflow', { ...parameters, namespace: 'canary', runId: '47e33895-aff5-475a-9b53-73abdee8bebe', @@ -80,7 +194,7 @@ describe('API Request Encoding', () => { 'temporal.canary.cron-workflow.sanity-2022-05-02T16:03:11-06:00/workflow.advanced-visibility.scan', }); expect(route).toBe( - 'http://localhost:8080/api/v1/namespaces/canary/workflows/temporal.canary.cron-workflow.sanity-2022-05-02T16%253A03%253A11-06%253A00%252Fworkflow.advanced-visibility.scan/runs/47e33895-aff5-475a-9b53-73abdee8bebe', + 'http://localhost:8233/api/v1/namespaces/canary/workflows/temporal.canary.cron-workflow.sanity-2022-05-02T16%3A03%3A11-06%3A00%2Fworkflow.advanced-visibility.scan', ); }); }); diff --git a/src/lib/utilities/route-for-api.ts b/src/lib/utilities/route-for-api.ts index e2397ccb1..142e83f4b 100644 --- a/src/lib/utilities/route-for-api.ts +++ b/src/lib/utilities/route-for-api.ts @@ -1,5 +1,50 @@ +import { get } from 'svelte/store'; + +import { base as basePath } from '$app/paths'; +import { page } from '$app/stores'; + +import type { + APIRouteParameters, + APIRoutePath, + BatchAPIRoutePath, + BatchRouteParameters, + NamespaceAPIRoutePath, + NamespaceRouteParameters, + NexusAPIRoutePath, + NexusRouteParameters, + ParameterlessAPIRoutePath, + ScheduleAPIRoutePath, + ScheduleListRouteParameters, + ScheduleRouteParameters, + SchedulesAPIRoutePath, + SearchAttributesRouteParameters, + SearchAttributesRoutePath, + TaskQueueAPIRoutePath, + TaskQueueRouteParameters, + WorkerAPIRoutePath, + WorkerDeploymentAPIRoutePath, + WorkerDeploymentListRouteParameters, + WorkerDeploymentRouteParameters, + WorkerDeploymentsAPIRoutePath, + WorkerDeploymentVersionAPIRoutePath, + WorkerDeploymentVersionRouteParameters, + WorkflowActivitiesAPIRoutePath, + WorkflowActivitiesRouteParameters, + WorkflowAPIRoutePath, + WorkflowListRouteParameters, + WorkflowQueryAPIRoutePath, + WorkflowQueryRouteParameters, + WorkflowRawHistoryRouteParameters, + WorkflowRouteParameters, + WorkflowsAPIRoutePath, + WorkflowSignalAPIRoutePath, + WorkflowSignalRouteParameters, + WorkflowUpdateAPIRoutePath, + WorkflowUpdateRouteParameters, +} from '$lib/types/api'; + import { getApiOrigin } from './get-api-origin'; -import { publicPath } from './get-public-path'; +import { minimumVersionRequired } from './version-check'; const replaceNamespaceInApiUrl = ( apiUrl: string, @@ -11,112 +56,219 @@ const replaceNamespaceInApiUrl = ( return ''; }; -const base = async (namespace?: string): Promise => { +export const base = (namespace?: string): string => { let baseUrl = ''; + const webUrl: string | undefined = get(page).data?.webUrl; + + const webUrlExistsWithNamespace = webUrl && namespace; + const apiUrlExistsWithNamespace = globalThis?.AppConfig?.apiUrl && namespace; - if (globalThis?.GetNamespaces && namespace) { - const namespaces = await globalThis.GetNamespaces(); - const configNamespace = namespaces?.find((n) => n.namespace === namespace); - baseUrl = - configNamespace?.webUri ?? - replaceNamespaceInApiUrl(globalThis?.AppConfig?.apiUrl, namespace); + if (webUrlExistsWithNamespace) { + baseUrl = webUrl; + } else if (apiUrlExistsWithNamespace) { + console.warn('Using fallback api url, web url not found'); + baseUrl = replaceNamespaceInApiUrl(globalThis.AppConfig.apiUrl, namespace); } else { baseUrl = getApiOrigin(); } if (baseUrl.endsWith('/')) baseUrl = baseUrl.slice(0, -1); - baseUrl = `${baseUrl}${publicPath}`; + baseUrl = `${baseUrl}${basePath}`; return baseUrl; }; -const withBase = async ( - endpoint: string, - namespace?: string, -): Promise => { +const getPath = (endpoint: string): string => { if (endpoint.startsWith('/')) endpoint = endpoint.slice(1); - const baseUrl = await base(namespace); - return `${baseUrl}/api/v1/${endpoint}`; + return `/api/v1/${endpoint}`; }; -const encode = (parameters: APIRouteParameters): APIRouteParameters => { +const withBase = (path: string, namespace?: string): string => { + const baseUrl = base(namespace); + return `${baseUrl}${path}`; +}; + +const encode = ( + parameters: Partial, +): APIRouteParameters => { + const version = get(page)?.data?.settings?.version; return Object.keys(parameters ?? {}).reduce( (acc, key) => { - acc[key] = encodeURIComponent(encodeURIComponent(parameters[key])); + if (version && minimumVersionRequired('2.23.0', version)) { + acc[key] = encodeURIComponent(parameters[key]); + } else { + acc[key] = encodeURIComponent(encodeURIComponent(parameters[key])); + } return acc; }, { namespace: '', workflowId: '', scheduleId: '', - runId: '', queue: '', + queryType: '', + signalName: '', + updateName: '', + batchJobId: '', + runId: '', + activityId: '', + endpointId: '', + deploymentName: '', + version: '', }, ); }; +export function pathForApi( + route: APIRoutePath, + parameters?: Partial, + shouldEncode = true, +): string { + if (shouldEncode) parameters = encode(parameters); + + const routes: { [K in APIRoutePath]: string } = { + systemInfo: '/system-info', + cluster: '/cluster-info', + namespaces: '/namespaces', + namespace: `/namespaces/${parameters?.namespace}`, + 'search-attributes': `/namespaces/${parameters.namespace}/search-attributes`, + 'events.raw': `/namespaces/${parameters?.namespace}/workflows/${parameters?.workflowId}/run/${parameters?.runId}/history.json`, + 'events.ascending': `/namespaces/${parameters?.namespace}/workflows/${parameters?.workflowId}/history`, + 'events.descending': `/namespaces/${parameters?.namespace}/workflows/${parameters?.workflowId}/history-reverse`, + query: `/namespaces/${parameters?.namespace}/workflows/${parameters?.workflowId}/query/${parameters.queryType}`, + schedule: `/namespaces/${parameters?.namespace}/schedules/${parameters?.scheduleId}`, + 'schedule.patch': `/namespaces/${parameters?.namespace}/schedules/${parameters?.scheduleId}/patch`, + 'schedule.edit': `/namespaces/${parameters?.namespace}/schedules/${parameters?.scheduleId}/update`, + schedules: `/namespaces/${parameters?.namespace}/schedules`, + settings: '/settings', + 'task-queue': `/namespaces/${parameters?.namespace}/task-queues/${parameters?.queue}`, + 'task-queue.compatibility': `/namespaces/${parameters?.namespace}/task-queues/${parameters?.queue}/worker-build-id-compatibility`, + 'task-queue.rules': `/namespaces/${parameters?.namespace}/task-queues/${parameters?.queue}/worker-versioning-rules`, + user: '/me', + 'worker-task-reachability': `/namespaces/${parameters?.namespace}/worker-task-reachability`, + 'workflow.terminate': `/namespaces/${parameters?.namespace}/workflows/${parameters?.workflowId}/terminate`, + 'workflow.cancel': `/namespaces/${parameters.namespace}/workflows/${parameters.workflowId}/cancel`, + 'workflow.signal': `/namespaces/${parameters.namespace}/workflows/${parameters.workflowId}/signal/${parameters.signalName}`, + 'workflow.update': `/namespaces/${parameters.namespace}/workflows/${parameters.workflowId}/update/${parameters.updateName}`, + 'workflow.reset': `/namespaces/${parameters.namespace}/workflows/${parameters.workflowId}/reset`, + workflow: `/namespaces/${parameters?.namespace}/workflows/${parameters?.workflowId}`, + workflows: `/namespaces/${parameters?.namespace}/workflows`, + 'workflows.archived': `/namespaces/${parameters?.namespace}/archived-workflows`, + 'workflows.count': `/namespaces/${parameters?.namespace}/workflow-count`, + 'activity.complete': `/namespaces/${parameters.namespace}/activities/complete-by-id`, + 'activity.fail': `/namespaces/${parameters.namespace}/activities/fail-by-id`, + 'activity.pause': `/namespaces/${parameters.namespace}/activities/pause`, + 'activity.unpause': `/namespaces/${parameters.namespace}/activities/unpause`, + 'activity.reset': `/namespaces/${parameters.namespace}/activities/reset`, + 'activity.update-options': `/namespaces/${parameters.namespace}/activities/update-options`, + 'batch-operations.list': `/namespaces/${parameters.namespace}/batch-operations`, + 'batch-operations': `/namespaces/${parameters.namespace}/batch-operations/${parameters?.batchJobId}`, + 'nexus-endpoints': '/nexus/endpoints', + 'nexus-endpoint': `/nexus/endpoints/${parameters.endpointId}`, + 'nexus-endpoint.update': `/nexus/endpoints/${parameters.endpointId}/update`, + 'worker-deployments': `/namespaces/${parameters.namespace}/worker-deployments`, + 'worker-deployment': `/namespaces/${parameters.namespace}/worker-deployments/${parameters.deploymentName}`, + 'worker-deployment-version': `/namespaces/${parameters.namespace}/worker-deployment-versions/${parameters.version}`, + }; + + return getPath(routes[route]); +} + export function routeForApi( route: WorkflowsAPIRoutePath, parameters: WorkflowListRouteParameters, shouldEncode?: boolean, -): Promise; +): string; export function routeForApi( route: NamespaceAPIRoutePath, parameters: NamespaceRouteParameters, shouldEncode?: boolean, -): Promise; +): string; export function routeForApi( route: SchedulesAPIRoutePath, parameters: ScheduleListRouteParameters, -): Promise; +): string; +export function routeForApi( + route: WorkerAPIRoutePath, + parameters: NamespaceRouteParameters, + shouldEncode?: boolean, +): string; export function routeForApi( route: WorkflowAPIRoutePath, parameters: WorkflowRouteParameters, shouldEncode?: boolean, -): Promise; +): string; +export function routeForApi( + route: WorkflowAPIRoutePath, + parameters: WorkflowRawHistoryRouteParameters, + shouldEncode?: boolean, +): string; +export function routeForApi( + route: WorkflowSignalAPIRoutePath, + parameters: WorkflowSignalRouteParameters, + shouldEncode?: boolean, +): string; +export function routeForApi( + route: WorkflowUpdateAPIRoutePath, + parameters: WorkflowUpdateRouteParameters, + shouldEncode?: boolean, +): string; +export function routeForApi( + route: WorkflowQueryAPIRoutePath, + parameters: WorkflowQueryRouteParameters, + shouldEncode?: boolean, +): string; +export function routeForApi( + route: WorkflowActivitiesAPIRoutePath, + parameters: WorkflowActivitiesRouteParameters, + shouldEncode?: boolean, +): string; +export function routeForApi( + route: BatchAPIRoutePath, + parameters: BatchRouteParameters, + shouldEncode?: boolean, +): string; export function routeForApi( route: ScheduleAPIRoutePath, parameters: ScheduleRouteParameters, shouldEncode?: boolean, -): Promise; +): string; export function routeForApi( route: TaskQueueAPIRoutePath, parameters: TaskQueueRouteParameters, shouldEncode?: boolean, -): Promise; +): string; +export function routeForApi( + route: SearchAttributesRoutePath, + parameters: SearchAttributesRouteParameters, +): string; +export function routeForApi( + route: NexusAPIRoutePath, + parameters: NexusRouteParameters, + shouldEncode?: boolean, +): string; +export function routeForApi( + route: WorkerDeploymentsAPIRoutePath, + parameters: WorkerDeploymentListRouteParameters, + shouldEncode?: boolean, +): string; export function routeForApi( - route: ParameterlessAPIRoutePath | SearchAttributesRoutePath, -): Promise; + route: WorkerDeploymentAPIRoutePath, + parameters: WorkerDeploymentRouteParameters, + shouldEncode?: boolean, +): string; +export function routeForApi( + route: WorkerDeploymentVersionAPIRoutePath, + parameters: WorkerDeploymentVersionRouteParameters, + shouldEncode?: boolean, +): string; +export function routeForApi(route: ParameterlessAPIRoutePath): string; export function routeForApi( route: APIRoutePath, parameters?: APIRouteParameters, shouldEncode = true, -): Promise { - if (shouldEncode) parameters = encode(parameters); - - const routes: { [K in APIRoutePath]: string } = { - cluster: '/cluster', - 'events.ascending': `/namespaces/${parameters?.namespace}/workflows/${parameters?.workflowId}/runs/${parameters?.runId}/events`, - 'events.descending': `/namespaces/${parameters?.namespace}/workflows/${parameters?.workflowId}/runs/${parameters?.runId}/events/reverse`, - namespaces: '/namespaces', - namespace: `/namespaces/${parameters?.namespace}`, - query: `/namespaces/${parameters?.namespace}/workflows/${parameters?.workflowId}/runs/${parameters?.runId}/query`, - 'schedule.delete': `/namespaces/${parameters?.namespace}/schedules/${parameters?.scheduleId}`, - schedule: `/namespaces/${parameters?.namespace}/schedules/${parameters?.scheduleId}`, - schedules: `/namespaces/${parameters?.namespace}/schedules`, - 'search-attributes': '/search-attributes', - settings: '/settings', - 'task-queue': `/namespaces/${parameters?.namespace}/task-queues/${parameters?.queue}`, - user: '/me', - 'workflow.terminate': `/namespaces/${parameters?.namespace}/workflows/${parameters?.workflowId}/runs/${parameters?.runId}/terminate`, - 'workflow.cancel': `/namespaces/${parameters.namespace}/workflows/${parameters.workflowId}/runs/${parameters.runId}/cancel`, - workflow: `/namespaces/${parameters?.namespace}/workflows/${parameters?.workflowId}/runs/${parameters?.runId}`, - 'workflows.archived': `/namespaces/${parameters?.namespace}/workflows/archived`, - workflows: `/namespaces/${parameters?.namespace}/workflows`, - 'workflows.count': `/namespaces/${parameters?.namespace}/workflows/count`, - 'workflows.batch.terminate': `/namespaces/${parameters.namespace}/workflows/batch/terminate`, - 'workflows.batch.describe': `/namespaces/${parameters.namespace}/workflows/batch/describe`, - }; +): string { + const path = pathForApi(route, parameters, shouldEncode); - return withBase(routes[route], parameters?.namespace); + return withBase(path, parameters?.namespace); } diff --git a/src/lib/utilities/route-for-api.with-api-url.test.ts b/src/lib/utilities/route-for-api.with-api-url.test.ts deleted file mode 100644 index 90ce11407..000000000 --- a/src/lib/utilities/route-for-api.with-api-url.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { describe, expect, it, vi } from 'vitest'; -import { routeForApi } from './route-for-api'; - -const parameters = { - namespace: 'namespace', - workflowId: 'workflow', - runId: 'run', - queue: 'queue', -}; - -vi.stubGlobal('GetNamespaces', async () => { - return [ - { - namespace: 'namespace', - webUri: 'https://api.url/', - }, - ]; -}); - -describe('routeForApi with GetNamespaces', () => { - it('should return a route for workflow', async () => { - const route = await routeForApi('workflow', parameters); - expect(route).toBe( - 'https://api.url/api/v1/namespaces/namespace/workflows/workflow/runs/run', - ); - }); -}); diff --git a/src/lib/utilities/route-for.test.ts b/src/lib/utilities/route-for.test.ts index 875588528..3905bfa0c 100644 --- a/src/lib/utilities/route-for.test.ts +++ b/src/lib/utilities/route-for.test.ts @@ -1,28 +1,28 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; + import { - routeForEventHistory, - routeForAuthentication, - routeForStackTrace, - routeForWorkers, - routeForWorkflow, - routeForWorkflowQuery, - routeForWorkflows, - routeForImport, - routeForLoginPage, hasParameters, isEventHistoryParameters, - isWorkflowParameters, isEventParameters, + isNamespaceParameter, + isWorkflowParameters, + routeForArchivalWorkfows, + routeForAuthentication, + routeForCallStack, + routeForEventHistory, + routeForEventHistoryImport, + routeForLoginPage, routeForNamespace, routeForNamespaces, - isEventView, - routeForArchivalWorkfows, routeForPendingActivities, - routeForSchedules, - routeForScheduleCreate, routeForSchedule, + routeForScheduleCreate, + routeForSchedules, routeForTaskQueue, - isNamespaceParameter, + routeForWorkers, + routeForWorkflow, + routeForWorkflowQuery, + routeForWorkflows, routeForWorkflowsWithQuery, } from './route-for'; @@ -43,10 +43,9 @@ describe('routeFor', () => { const path = routeForWorkflowsWithQuery({ namespace: 'default', query: 'ExecutionStatus="Running"', - search: 'basic', }); expect(path).toBe( - '/namespaces/default/workflows?query=ExecutionStatus%3D%22Running%22&search=basic', + '/namespaces/default/workflows?query=ExecutionStatus%3D%22Running%22', ); }); @@ -69,43 +68,13 @@ describe('routeFor', () => { expect(path).toBe('/namespaces/default/workflows/abc/def'); }); - it('should route to "workflow.events" feed page', () => { - const path = routeForEventHistory({ - namespace: 'default', - workflow: 'abc', - run: 'def', - view: 'feed', - }); - expect(path).toBe('/namespaces/default/workflows/abc/def/history/feed'); - }); - - it('should route to "workflow.events" compact page', () => { - const path = routeForEventHistory({ - namespace: 'default', - workflow: 'abc', - run: 'def', - view: 'compact', - }); - expect(path).toBe('/namespaces/default/workflows/abc/def/history/compact'); - }); - - it('should route to "workflow.events" json page', () => { - const path = routeForEventHistory({ - namespace: 'default', - workflow: 'abc', - run: 'def', - view: 'json', - }); - expect(path).toBe('/namespaces/default/workflows/abc/def/history/json'); - }); - - it('should route to default "workflow.events" feed page if no view provided', () => { + it('should route to "workflow.events" history page', () => { const path = routeForEventHistory({ namespace: 'default', workflow: 'abc', run: 'def', }); - expect(path).toBe('/namespaces/default/workflows/abc/def/history/feed'); + expect(path).toBe('/namespaces/default/workflows/abc/def/history'); }); it('should route to pending activities', () => { @@ -119,13 +88,13 @@ describe('routeFor', () => { ); }); - it('should route to "workflow".stack-trace', () => { - const path = routeForStackTrace({ + it('should route to "workflow".call-stack', () => { + const path = routeForCallStack({ namespace: 'default', workflow: 'abc', run: 'def', }); - expect(path).toBe('/namespaces/default/workflows/abc/def/stack-trace'); + expect(path).toBe('/namespaces/default/workflows/abc/def/call-stack'); }); it('should route to "workflow".query', () => { @@ -165,18 +134,18 @@ describe('routeFor', () => { describe('routeFor import ', () => { it('should default route to "import/events" for import', () => { - const path = routeForImport({ - importType: 'events', - }); + const path = routeForEventHistoryImport(); expect(path).toBe('/import/events'); }); - it('should route to specific view for import', () => { - const path = routeForImport({ - importType: 'events', - view: 'compact', - }); - expect(path).toBe('/import/events/namespace/workflow/run/history/compact'); + it('should route to specific namespace and view for import', () => { + const path = routeForEventHistoryImport('default', 'compact'); + expect(path).toBe('/import/events/default/workflow/run/history/compact'); + }); + + it('should route to root import if missing namespace', () => { + const path = routeForEventHistoryImport(undefined, 'compact'); + expect(path).toBe('/import/events'); }); it('should return the correct route for routeForSchedules', () => { @@ -296,7 +265,7 @@ describe('routeFor SSO authentication ', () => { const ssoUrl = new URL(sso); expect(ssoUrl.searchParams.get('returnUrl')).toBe( - `https://localhost/some/path`, + 'https://localhost/some/path', ); }); @@ -400,55 +369,41 @@ describe('routeFor SSO authentication ', () => { namespace: 'default', workflow: 'workflow', run: 'run', - view: 'feed', eventId: '1234', }), ).toBe(true); + }); - it('should return false if all of the required parametersisWorkflowParameters$x are provided', () => { - expect( - isWorkflowParameters({ - namespace: 'default', - workflow: 'workflow', - }), - ).toBe(false); - }); - - it('should return false if all of the required parameters for isEventHistoryParameters are provided', () => { - expect( - isEventHistoryParameters({ - namespace: 'default', - workflow: 'workflow', - run: 'run', - view: 'feed', - }), - ).toBe(false); - }); - - it('should return false if all of the required parameters for isEventParameters are provided', () => { - expect( - isEventParameters({ - namespace: 'default', - workflow: 'workflow', - run: 'run', - view: 'feed', - }), - ).toBe(false); - }); + it('should return false if all of the required parametersisWorkflowParameters$x are provided', () => { + expect( + isWorkflowParameters({ + namespace: 'default', + workflow: 'workflow', + }), + ).toBe(false); }); - }); -}); -describe('isEventView', () => { - for (const validEventView of ['feed', 'compact', 'json']) { - it(`should return true if provided "${validEventView}"`, () => { - expect(isEventView(validEventView)).toBe(true); + it('should return false if all of the required parameters for isEventHistoryParameters are provided', () => { + expect( + isEventHistoryParameters({ + namespace: 'default', + workflow: 'workflow', + run: 'run', + view: 'feed', + }), + ).toBe(false); }); - it('should return false if given a bogus event view', () => { - expect(isEventView('bogus')).toBe(false); + it('should return false if all of the required parameters for isEventParameters are provided', () => { + expect( + isEventParameters({ + namespace: 'default', + workflow: 'workflow', + run: 'run', + }), + ).toBe(false); }); - } + }); }); describe('isNamespaceParameter', () => { diff --git a/src/lib/utilities/route-for.ts b/src/lib/utilities/route-for.ts index 9a5af1ac9..e0559937d 100644 --- a/src/lib/utilities/route-for.ts +++ b/src/lib/utilities/route-for.ts @@ -1,7 +1,11 @@ -import { browser } from '$app/env'; -import { toURL } from '$lib/utilities/to-url'; -import { publicPath } from '$lib/utilities/get-public-path'; +import { BROWSER } from 'esm-env'; + +import { base } from '$app/paths'; + +import type { EventView } from '$lib/types/events'; +import type { Settings } from '$lib/types/global'; import { encodeURIForSvelte } from '$lib/utilities/encode-uri'; +import { toURL } from '$lib/utilities/to-url'; type RouteParameters = { namespace: string; @@ -9,25 +13,21 @@ type RouteParameters = { run: string; view?: EventView | string; queryParams?: Record; - eventId: string; + eventId?: string; + eventType?: string; + requestId?: string; scheduleId: string; queue: string; schedule: string; query?: string; search?: string; -}; - -export const isEventView = (view: string): view is EventView => { - if (view === 'feed') return true; - if (view === 'compact') return true; - if (view === 'json') return true; - return false; + page?: string; }; export type NamespaceParameter = Pick; export type WorkflowsParameter = Pick< RouteParameters, - 'namespace' | 'query' | 'search' + 'namespace' | 'query' | 'page' >; export type TaskQueueParameters = Pick; export type WorkflowParameters = Pick< @@ -40,10 +40,17 @@ export type ScheduleParameters = Pick< >; export type EventHistoryParameters = Pick< RouteParameters, - 'namespace' | 'workflow' | 'run' | 'view' | 'queryParams' + 'namespace' | 'workflow' | 'run' | 'eventId' | 'view' | 'queryParams' >; -export type EventParameters = Required< - Pick +export type EventParameters = Pick< + RouteParameters, + | 'namespace' + | 'workflow' + | 'run' + | 'view' + | 'eventId' + | 'eventType' + | 'requestId' >; export type AuthenticationParameters = { @@ -53,33 +60,67 @@ export type AuthenticationParameters = { }; export const routeForNamespaces = (): string => { - return `${publicPath}/namespaces`; + return `${base}/namespaces`; +}; + +export const routeForNexus = (): string => { + return `${base}/nexus`; +}; + +export const routeForNexusEndpoint = (id: string): string => { + return `${base}/nexus/${id}`; +}; + +export const routeForNexusEndpointEdit = (id: string): string => { + return `${base}/nexus/${id}/edit`; +}; + +export const routeForNexusEndpointCreate = (): string => { + return `${base}/nexus/create`; }; export const routeForNamespace = ({ namespace, }: NamespaceParameter): string => { - return `${publicPath}/namespaces/${namespace}`; + return `${base}/namespaces/${namespace}`; }; export const routeForNamespaceSelector = () => { - return `${publicPath}/select-namespace`; + return `${base}/select-namespace`; }; export const routeForWorkflows = (parameters: NamespaceParameter): string => { return `${routeForNamespace(parameters)}/workflows`; }; +type StartWorkflowParameters = NamespaceParameter & + Partial<{ workflowId: string; taskQueue: string; workflowType: string }>; +export const routeForWorkflowStart = ({ + namespace, + workflowId, + taskQueue, + workflowType, +}: StartWorkflowParameters): string => { + return toURL(`${routeForNamespace({ namespace })}/workflows/start-workflow`, { + workflowId: workflowId || '', + taskQueue: taskQueue || '', + workflowType: workflowType || '', + }); +}; + export const routeForWorkflowsWithQuery = ({ namespace, query, - search, -}: WorkflowsParameter): string => { - if (!browser) { + page, +}: WorkflowsParameter): string | undefined => { + if (!BROWSER) { return undefined; } - return toURL(routeForWorkflows({ namespace }), { query, search }); + return toURL(routeForWorkflows({ namespace }), { + query, + ...(page && { page }), + }); }; export const routeForArchivalWorkfows = ( @@ -127,19 +168,65 @@ export const routeForScheduleEdit = ({ }; export const routeForEventHistory = ({ - view, queryParams, ...parameters }: EventHistoryParameters): string => { const eventHistoryPath = `${routeForWorkflow(parameters)}/history`; - if (!view || !isEventView(view)) return `${eventHistoryPath}/feed`; - return toURL(`${eventHistoryPath}/${view}`, queryParams); + return toURL(`${eventHistoryPath}`, queryParams); +}; + +export const routeForEventHistoryEvent = ({ + eventId, + requestId, + ...parameters +}: EventParameters): string => { + return `${routeForWorkflow(parameters)}/history/events/${eventId || requestId}`; }; export const routeForWorkers = (parameters: WorkflowParameters): string => { return `${routeForWorkflow(parameters)}/workers`; }; +export const routeForWorkerDeployments = ({ + namespace, +}: { + namespace: string; +}) => { + return `${base}/namespaces/${namespace}/worker-deployments`; +}; + +export const routeForWorkerDeployment = ({ + namespace, + deployment, +}: { + namespace: string; + deployment: string; +}) => { + const deploymentName = encodeURIForSvelte(deployment); + return `${base}/namespaces/${namespace}/worker-deployments/${deploymentName}`; +}; + +export const routeForWorkerDeploymentVersion = ({ + namespace, + deployment, + version, +}: { + namespace: string; + deployment: string; + version: string; +}) => { + return `${routeForWorkerDeployment({ + namespace, + deployment, + })}/version/${version}`; +}; + +export const routeForRelationships = ( + parameters: WorkflowParameters, +): string => { + return `${routeForWorkflow(parameters)}/relationships`; +}; + export const routeForTaskQueue = (parameters: TaskQueueParameters): string => { const queue = encodeURIForSvelte(parameters.queue); @@ -148,8 +235,8 @@ export const routeForTaskQueue = (parameters: TaskQueueParameters): string => { })}/task-queues/${queue}`; }; -export const routeForStackTrace = (parameters: WorkflowParameters): string => { - return `${routeForWorkflow(parameters)}/stack-trace`; +export const routeForCallStack = (parameters: WorkflowParameters): string => { + return `${routeForWorkflow(parameters)}/call-stack`; }; export const routeForWorkflowQuery = ( @@ -158,6 +245,18 @@ export const routeForWorkflowQuery = ( return `${routeForWorkflow(parameters)}/query`; }; +export const routeForWorkflowMetadata = ( + parameters: WorkflowParameters, +): string => { + return `${routeForWorkflow(parameters)}/metadata`; +}; + +export const routeForWorkflowUpdate = ( + parameters: WorkflowParameters, +): string => { + return `${routeForWorkflow(parameters)}/update`; +}; + export const routeForPendingActivities = ( parameters: WorkflowParameters, ): string => { @@ -169,12 +268,13 @@ export const routeForAuthentication = ( ): string => { const { settings, searchParams: currentSearchParams, originUrl } = parameters; - const login = new URL(`${publicPath}/auth/sso`, settings.baseUrl); + const login = new URL(`${base}/auth/sso`, settings.baseUrl); let opts = settings.auth.options ?? []; opts = [...opts, 'returnUrl']; opts.forEach((option) => { + if (!currentSearchParams) return; const searchParam = currentSearchParams.get(option); if (searchParam) { login.searchParams.set(option, searchParam); @@ -188,9 +288,9 @@ export const routeForAuthentication = ( return login.toString(); }; -export const routeForLoginPage = (error = '', isBrowser = browser): string => { +export const routeForLoginPage = (error = '', isBrowser = BROWSER): string => { if (isBrowser) { - const login = new URL(`${publicPath}/login`, window.location.origin); + const login = new URL(`${base}/login`, window.location.origin); login.searchParams.set('returnUrl', window.location.href); if (error) { login.searchParams.set('error', error); @@ -198,22 +298,35 @@ export const routeForLoginPage = (error = '', isBrowser = browser): string => { return login.toString(); } - return `${publicPath}/login`; + return `${base}/login`; }; -type ImportParameters = { - importType: 'events'; - view?: EventView; +export const routeForEventHistoryImport = ( + namespace?: string, + view?: EventView, +): string => { + if (namespace && view) { + return `${base}/import/events/${namespace}/workflow/run/history/${view}`; + } + return `${base}/import/events`; }; -export const routeForImport = ({ - importType, - view, -}: ImportParameters): string => { - if (importType === 'events' && view) { - return `${publicPath}/import/${importType}/namespace/workflow/run/history/${view}`; - } - return `${publicPath}/import/${importType}`; +export const routeForBatchOperations = ({ + namespace, +}: { + namespace: string; +}) => { + return `${base}/namespaces/${namespace}/batch-operations`; +}; + +export const routeForBatchOperation = ({ + namespace, + jobId, +}: { + namespace: string; + jobId: string; +}) => { + return `${base}/namespaces/${namespace}/batch-operations/${jobId}`; }; export const hasParameters = @@ -250,6 +363,5 @@ export const isEventParameters = hasParameters( 'namespace', 'workflow', 'run', - 'view', 'eventId', ); diff --git a/src/lib/utilities/schedule-comment-formatting.test.ts b/src/lib/utilities/schedule-comment-formatting.test.ts deleted file mode 100644 index 47cfcf14c..000000000 --- a/src/lib/utilities/schedule-comment-formatting.test.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { - calendarToComment, - intervalToComment, -} from './schedule-comment-formatting'; -import { timeToInterval } from './schedule-data-formatting'; - -describe('calendarToComment', () => { - describe('format days of week calendar', () => { - it(`should return correct comment for weekends`, () => { - const options = { - preset: 'week', - month: '', - dayOfMonth: '', - dayOfWeek: '6,7', - hour: '12', - minute: '30', - }; - - expect(calendarToComment(options)).toBe('Weekends at 12:30pm'); - }); - - it(`should return correct comment for weekdays`, () => { - const options = { - preset: 'week', - month: '', - dayOfMonth: '', - dayOfWeek: '1,2,3,4,5', - hour: '17', - minute: '46', - }; - - expect(calendarToComment(options)).toBe('Weekdays at 05:46pm'); - }); - - it(`should return correct comment for a range of days`, () => { - const options = { - preset: 'week', - month: '', - dayOfMonth: '', - dayOfWeek: '1,2,5', - hour: '4', - minute: '15', - }; - - expect(calendarToComment(options)).toBe( - 'Monday, Tuesday, Friday at 04:15am', - ); - }); - - it(`should return correct comment for a range of days`, () => { - const options = { - preset: 'week', - month: '10', - dayOfMonth: '4', - dayOfWeek: '5,6,7', - hour: '12', - minute: '0', - }; - - expect(calendarToComment(options)).toBe( - 'Friday, Saturday, Sunday at 12:00pm', - ); - }); - }); - - describe('format days of month calendar', () => { - it(`should return correct comment for all months and single day`, () => { - const options = { - preset: 'month', - month: '*', - dayOfMonth: '1', - dayOfWeek: '', - hour: '23', - minute: '5', - }; - - expect(calendarToComment(options)).toBe( - 'Every 1 of the month at 11:05pm', - ); - }); - - it(`should return correct comment for all months and multiple day`, () => { - const options = { - preset: 'month', - month: '*', - dayOfMonth: '3,13,19,28', - dayOfWeek: '', - hour: '00', - minute: '05', - }; - - expect(calendarToComment(options)).toBe( - 'Every 3,13,19,28 of the month at 12:05am', - ); - }); - - it(`should return correct comment for single months and single day`, () => { - const options = { - preset: 'month', - month: '2', - dayOfMonth: '3', - dayOfWeek: '', - hour: '04', - minute: '05', - }; - - expect(calendarToComment(options)).toBe('Every 3 of February at 04:05am'); - }); - - it(`should return correct comment for single months and multiple days`, () => { - const options = { - preset: 'month', - month: '2', - dayOfMonth: '3,4,5,11', - dayOfWeek: '', - hour: '16', - minute: '05', - }; - - expect(calendarToComment(options)).toBe( - 'Every 3,4,5,11 of February at 04:05pm', - ); - }); - - it(`should return correct comment for multiple months and single day`, () => { - const options = { - preset: 'month', - month: '6,7,8', - dayOfMonth: '4', - dayOfWeek: '', - hour: '19', - minute: '45', - }; - - expect(calendarToComment(options)).toBe( - 'Every 4 of June, July, August at 07:45pm', - ); - }); - - it(`should return correct comment for multiple months and multiple days`, () => { - const options = { - preset: 'month', - month: '1,10,11,12', - dayOfMonth: '1,15,30', - dayOfWeek: '', - hour: '6', - minute: '16', - }; - - expect(calendarToComment(options)).toBe( - 'Every 1,15,30 of January, October, November, December at 06:16am', - ); - }); - }); -}); - -describe('intervalToComment', () => { - it(`should return correct comment for only days interval`, () => { - const interval = timeToInterval('15', '', '', ''); - expect(intervalToComment(interval)).toBe('Every 15days:00hrs:00min:00sec'); - }); - - it(`should return correct comment for days and hours interval`, () => { - const interval = timeToInterval('3', '6', '', ''); - expect(intervalToComment(interval)).toBe('Every 3days:06hrs:00min:00sec'); - }); - - it(`should return correct comment for days and hours offset`, () => { - const offset = timeToInterval('3', '6', '', ''); - expect(intervalToComment(offset, true)).toBe( - 'Offset 3days:06hrs:00min:00sec', - ); - }); - - it(`should return correct comment for days and hours and minutes interval`, () => { - const interval = timeToInterval('3', '6', '3', ''); - expect(intervalToComment(interval)).toBe('Every 3days:06hrs:03min:00sec'); - }); - - it(`should return correct comment for days and hours and minutes and seconds interval`, () => { - const interval = timeToInterval('2', '19', '59', '17'); - expect(intervalToComment(interval)).toBe('Every 2days:19hrs:59min:17sec'); - }); - - it(`should return correct comment for only hours interval`, () => { - const interval = timeToInterval('', '8', '', ''); - expect(intervalToComment(interval)).toBe('Every 08hrs:00min:00sec'); - }); - - it(`should return correct comment for hours and minutes interval`, () => { - const interval = timeToInterval('', '12', '35', ''); - expect(intervalToComment(interval)).toBe('Every 12hrs:35min:00sec'); - }); - - it(`should return correct comment for hours and minutes and seconds interval`, () => { - const interval = timeToInterval('', '2', '15', '30'); - expect(intervalToComment(interval)).toBe('Every 02hrs:15min:30sec'); - }); - - it(`should return correct comment for hours and minutes and seconds offset`, () => { - const offset = timeToInterval('', '2', '15', '30'); - expect(intervalToComment(offset, true)).toBe('Offset 02hrs:15min:30sec'); - }); - - it(`should return correct comment for only minutes interval`, () => { - const interval = timeToInterval('', '', '45', ''); - expect(intervalToComment(interval)).toBe('Every 45min:00sec'); - }); - - it(`should return correct comment for minutes and seconds interval`, () => { - const interval = timeToInterval('', '', '10', '30'); - expect(intervalToComment(interval)).toBe('Every 10min:30sec'); - }); - - it(`should return correct comment for only minutes interval`, () => { - const interval = timeToInterval('', '', '', '50'); - expect(intervalToComment(interval)).toBe('Every 50sec'); - }); -}); diff --git a/src/lib/utilities/schedule-comment-formatting.ts b/src/lib/utilities/schedule-comment-formatting.ts deleted file mode 100644 index d92be60f2..000000000 --- a/src/lib/utilities/schedule-comment-formatting.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { genericWeekDays, monthNames, weekDays } from './calendar'; - -// Examples of output -// Every 3hrs:5min:20 sec 30 min offset -// Weekends at 12:00pm PST -// Sunday, Monday, Tuesday, Wednesday, Thursday, Friday at 12:00pm UTC -// Every 1, 15, 31 of the month at 12:00pm UTC -// Every 1, 15, 31 of January, March, April, June, July, September, December at 12:00pm UTC - -export const calendarToComment = ({ - preset, - month, - dayOfMonth, - dayOfWeek, - hour, - minute, -}: Partial): string => { - let comment = ''; - const time = !hour || !parseInt(hour) || parseInt(hour) < 12 ? 'am' : 'pm'; - const properHour = - !hour || !parseInt(hour) - ? '12' - : parseInt(hour) <= 12 - ? hour - : (parseInt(hour) - 12).toString(); - const timeStamp = `${properHour.padStart(2, '0')}:${ - minute ? minute.padStart(2, '0') : '00' - }${time}`; - - if (preset === 'week') { - const genericName = genericWeekDays.find( - (template) => template.value === dayOfWeek, - ); - if (genericName) { - comment = `${genericName.label} at ${timeStamp}`; - } else { - const split = dayOfWeek.split(','); - const splitNames = split - .map((d) => { - const day = weekDays.find((day) => day.value === d); - return day.label; - }) - .join(', '); - comment = `${splitNames} at ${timeStamp}`; - } - } else if (preset === 'month') { - if (month === '*') { - comment = `Every ${dayOfMonth} of the month at ${timeStamp}`; - } else { - const split = month.split(','); - const splitNames = split - .map((m) => { - const _month = monthNames.find((_m) => _m.value === m); - return _month.label; - }) - .join(', '); - comment = `Every ${dayOfMonth} of ${splitNames} at ${timeStamp}`; - } - } - - return comment; -}; - -export const intervalToComment = (interval = '', offset = false): string => { - let comment = ''; - if (!interval) return comment; - - const intervalAsNumber = parseInt(interval.slice(0, -1)); - - const days = Math.floor(intervalAsNumber / (60 * 60 * 24)); - let remainingSeconds = - intervalAsNumber - (days > 0 ? days * 60 * 60 * 24 : 0); - const hour = Math.floor(remainingSeconds / (60 * 60)); - remainingSeconds = remainingSeconds - (hour > 0 ? hour * 60 * 60 : 0); - const minute = Math.floor(remainingSeconds / 60); - const second = minute > 0 ? remainingSeconds - minute * 60 : remainingSeconds; - - const hourLabel = `${hour ? hour.toString().padStart(2, '0') : '00'}hrs`; - const minuteLabel = `${ - minute ? minute.toString().padStart(2, '0') : '00' - }min`; - const secondLabel = `${ - second ? second.toString().padStart(2, '0') : '00' - }sec`; - - const startingWord = offset ? 'Offset' : 'Every'; - if (days) { - comment = `${startingWord} ${days}days:${hourLabel}:${minuteLabel}:${secondLabel}`; - } else if (hour) { - comment = `${startingWord} ${hourLabel}:${minuteLabel}:${secondLabel}`; - } else if (minute) { - comment = `${startingWord} ${minuteLabel}:${secondLabel}`; - } else if (second) { - comment = `${startingWord} ${secondLabel}`; - } - - return comment; -}; diff --git a/src/lib/utilities/schedule-data-formatting.test.ts b/src/lib/utilities/schedule-data-formatting.test.ts index 0d8a8e54c..187de0096 100644 --- a/src/lib/utilities/schedule-data-formatting.test.ts +++ b/src/lib/utilities/schedule-data-formatting.test.ts @@ -1,11 +1,12 @@ import { describe, expect, it } from 'vitest'; + import { - timeToInterval, convertDaysAndMonths, + timeToInterval, } from './schedule-data-formatting'; describe('convertDaysAndMonths', () => { - it(`should return days and months if empty`, () => { + it('should return days and months if empty', () => { const months = []; const daysOfMonth = []; const daysOfWeek = []; @@ -19,67 +20,67 @@ describe('convertDaysAndMonths', () => { expect(dayOfWeek).toBe(''); }); - it(`should return all months`, () => { + it('should return all months', () => { const months = ['*']; const { month } = convertDaysAndMonths({ months }); expect(month).toBe('*'); }); - it(`should return single month`, () => { + it('should return single month', () => { const months = ['7']; const { month } = convertDaysAndMonths({ months }); expect(month).toBe('7'); }); - it(`should return sorted months`, () => { + it('should return sorted months', () => { const months = ['4', '8', '1', '12']; const { month } = convertDaysAndMonths({ months }); expect(month).toBe('1,4,8,12'); }); - it(`should return single day of month`, () => { + it('should return single day of month', () => { const daysOfMonth = [10]; const { dayOfMonth } = convertDaysAndMonths({ daysOfMonth }); expect(dayOfMonth).toBe('10'); }); - it(`should return sorted days of month`, () => { + it('should return sorted days of month', () => { const daysOfMonth = [19, 31, 1, 29, 8, 20, 4]; const { dayOfMonth } = convertDaysAndMonths({ daysOfMonth }); expect(dayOfMonth).toBe('1,4,8,19,20,29,31'); }); - it(`should return all days of week`, () => { + it('should return all days of week', () => { const daysOfWeek = ['*']; const { dayOfWeek } = convertDaysAndMonths({ daysOfWeek }); expect(dayOfWeek).toBe('*'); }); - it(`should return a single day of week`, () => { + it('should return a single day of week', () => { const daysOfWeek = ['5']; const { dayOfWeek } = convertDaysAndMonths({ daysOfWeek }); expect(dayOfWeek).toBe('5'); }); - it(`should return weekdays of week`, () => { + it('should return weekdays of week', () => { const daysOfWeek = ['1,2,3,4,5']; const { dayOfWeek } = convertDaysAndMonths({ daysOfWeek }); expect(dayOfWeek).toBe('1,2,3,4,5'); }); - it(`should return weekends of week`, () => { + it('should return weekends of week', () => { const daysOfWeek = ['6,7']; const { dayOfWeek } = convertDaysAndMonths({ daysOfWeek }); expect(dayOfWeek).toBe('6,7'); }); - it(`should return sorted range of days of week`, () => { + it('should return sorted range of days of week', () => { const daysOfWeek = ['6', '2', '5', '1']; const { dayOfWeek } = convertDaysAndMonths({ daysOfWeek }); expect(dayOfWeek).toBe('1,2,5,6'); }); - it(`should return sorted range of weekdays of week`, () => { + it('should return sorted range of weekdays of week', () => { const daysOfWeek = ['5', '4', '3', '2', '1']; const { dayOfWeek } = convertDaysAndMonths({ daysOfWeek }); expect(dayOfWeek).toBe('1,2,3,4,5'); @@ -87,52 +88,52 @@ describe('convertDaysAndMonths', () => { }); describe('timeToInterval', () => { - it(`should return correct interval for only day`, () => { + it('should return correct interval for only day', () => { const interval = timeToInterval('45', '', '', ''); expect(interval).toBe('3888000s'); }); - it(`should return correct interval for days and hours`, () => { + it('should return correct interval for days and hours', () => { const interval = timeToInterval('3', '6', '', ''); expect(interval).toBe('280800s'); }); - it(`should return correct interval for days and hours and minutes`, () => { + it('should return correct interval for days and hours and minutes', () => { const interval = timeToInterval('3', '6', '20', ''); expect(interval).toBe('282000s'); }); - it(`should return correct interval for days and hours and minutes and seconds`, () => { + it('should return correct interval for days and hours and minutes and seconds', () => { const interval = timeToInterval('3', '6', '20', '5'); expect(interval).toBe('282005s'); }); - it(`should return correct interval for only hours`, () => { + it('should return correct interval for only hours', () => { const interval = timeToInterval('', '8', '', ''); expect(interval).toBe('28800s'); }); - it(`should return correct interval for hours and minutes`, () => { + it('should return correct interval for hours and minutes', () => { const interval = timeToInterval('', '4', '23', ''); expect(interval).toBe('15780s'); }); - it(`should return correct interval for hours and minutes and seconds`, () => { + it('should return correct interval for hours and minutes and seconds', () => { const interval = timeToInterval('', '4', '30', '17'); expect(interval).toBe('16217s'); }); - it(`should return correct interval for only minutes`, () => { + it('should return correct interval for only minutes', () => { const interval = timeToInterval('', '', '10', ''); expect(interval).toBe('600s'); }); - it(`should return correct interval for minutes and seconds`, () => { + it('should return correct interval for minutes and seconds', () => { const interval = timeToInterval('', '', '30', '30'); expect(interval).toBe('1830s'); }); - it(`should return correct interval for only seconds`, () => { + it('should return correct interval for only seconds', () => { const interval = timeToInterval('', '', '', '30'); expect(interval).toBe('30s'); }); diff --git a/src/lib/utilities/schedule-data-formatting.ts b/src/lib/utilities/schedule-data-formatting.ts index 2eb43997c..94cee965a 100644 --- a/src/lib/utilities/schedule-data-formatting.ts +++ b/src/lib/utilities/schedule-data-formatting.ts @@ -1,3 +1,5 @@ +import type { ScheduleParameters } from '$lib/types/schedule'; + const parseTime = (time: string) => (time ? parseInt(time) : 0); export const timeToInterval = ( @@ -19,14 +21,18 @@ export const convertDaysAndMonths = ({ daysOfMonth = [], daysOfWeek = [], }: Partial): Partial => { - const month = months.sort((a, b) => parseInt(a) - parseInt(b)).join(','); - const dayOfMonth = daysOfMonth.sort((a, b) => a - b).join(','); + const month = months + .sort((a: string, b: string) => parseInt(a) - parseInt(b)) + .join(','); + const dayOfMonth = daysOfMonth + .sort((a: number, b: number) => a - b) + .join(','); const normalizedDaysOfWeek = daysOfWeek?.[0]?.split(',')?.length > 1 ? daysOfWeek[0].split(',') : daysOfWeek; const dayOfWeek = normalizedDaysOfWeek - .sort((a, b) => parseInt(a) - parseInt(b)) + .sort((a: string, b: string) => parseInt(a) - parseInt(b)) .join(','); return { month, dayOfMonth, dayOfWeek }; diff --git a/src/lib/utilities/screaming-enums.test.ts b/src/lib/utilities/screaming-enums.test.ts new file mode 100644 index 000000000..d5174e46a --- /dev/null +++ b/src/lib/utilities/screaming-enums.test.ts @@ -0,0 +1,278 @@ +import { describe, expect, it } from 'vitest'; + +import { EventType } from './is-event-type'; +import { + fromScreamingEnum, + toBatchOperationStateReadable, + toBatchOperationTypeReadable, + toEventNameReadable, + toNamespaceArchivalStateReadable, + toNamespaceStateReadable, + toSearchAttributeTypeReadable, + toWorkflowStatusReadable, + toWorkflowTaskFailureReadable, +} from './screaming-enums'; + +fromScreamingEnum; +describe('fromScreamingEnum', () => { + it('should return readable word from split with prefix', () => { + expect(fromScreamingEnum('THIS_IS_PREFIX_CATS_MEOW', 'ThisIsPrefix')).toBe( + 'CatsMeow', + ); + }); + it('should return original word from split with prefix if no underscore', () => { + expect(fromScreamingEnum('THIS-IS-PREFIX-CATS-MEOW', 'ThisIsPrefix')).toBe( + 'THIS-IS-PREFIX-CATS-MEOW', + ); + }); + it('should return empty string', () => { + expect(fromScreamingEnum('', 'prefix')).toBe(''); + }); + it('should return empty string', () => { + expect(fromScreamingEnum('', 'prefix')).toBe(''); + }); + it('should return undefined', () => { + expect(fromScreamingEnum(undefined, 'prefix')).toBe(undefined); + }); + it('should return original object value if not a string', () => { + expect(fromScreamingEnum({ test: 'this' }, 'prefix')).toStrictEqual({ + test: 'this', + }); + }); + it('should return original array value if not a string', () => { + expect(fromScreamingEnum([], 'prefix')).toStrictEqual([]); + }); +}); + +describe('toSearchAttributeTypeReadable', () => { + it('should return Keyword from search attribute enum', () => { + expect(toSearchAttributeTypeReadable('INDEXED_VALUE_TYPE_KEYWORD')).toBe( + 'Keyword', + ); + }); + it('should return Bool from search attribute enum', () => { + expect(toSearchAttributeTypeReadable('INDEXED_VALUE_TYPE_BOOL')).toBe( + 'Bool', + ); + }); + it('should return KeywordList from search attribute enum', () => { + expect( + toSearchAttributeTypeReadable('INDEXED_VALUE_TYPE_KEYWORD_LIST'), + ).toBe('KeywordList'); + }); + it('should return Datetime from search attribute enum', () => { + expect(toSearchAttributeTypeReadable('INDEXED_VALUE_TYPE_DATETIME')).toBe( + 'Datetime', + ); + }); + it('should return Int from search attribute enum', () => { + expect(toSearchAttributeTypeReadable('INDEXED_VALUE_TYPE_INT')).toBe('Int'); + }); + it('should return Double from search attribute enum', () => { + expect(toSearchAttributeTypeReadable('INDEXED_VALUE_TYPE_DOUBLE')).toBe( + 'Double', + ); + }); + it('should return Text from search attribute enum', () => { + expect(toSearchAttributeTypeReadable('INDEXED_VALUE_TYPE_TEXT')).toBe( + 'Text', + ); + }); + it('should return readable from unknown search attribute enum', () => { + expect(toSearchAttributeTypeReadable('INDEXED_VALUE_TYPE_BIG_INT')).toBe( + 'BigInt', + ); + }); + it('should return original enum from old search attribute enum', () => { + expect(toSearchAttributeTypeReadable('KeywordList')).toBe('KeywordList'); + }); +}); + +describe('toWorkflowStatusReadable', () => { + it('should return Running from ExecutionStatus enum', () => { + expect(toWorkflowStatusReadable('WORKFLOW_EXECUTION_STATUS_RUNNING')).toBe( + 'Running', + ); + }); + it('should return Completed from ExecutionStatus enum', () => { + expect( + toWorkflowStatusReadable('WORKFLOW_EXECUTION_STATUS_COMPLETED'), + ).toBe('Completed'); + }); + it('should return Failed from ExecutionStatus enum', () => { + expect(toWorkflowStatusReadable('WORKFLOW_EXECUTION_STATUS_FAILED')).toBe( + 'Failed', + ); + }); + it('should return Canceled from ExecutionStatus enum', () => { + expect(toWorkflowStatusReadable('WORKFLOW_EXECUTION_STATUS_CANCELED')).toBe( + 'Canceled', + ); + }); + it('should return Terminated from ExecutionStatus enum', () => { + expect( + toWorkflowStatusReadable('WORKFLOW_EXECUTION_STATUS_TERMINATED'), + ).toBe('Terminated'); + }); + it('should return ContinuedAsNew from ExecutionStatus enum', () => { + expect( + toWorkflowStatusReadable('WORKFLOW_EXECUTION_STATUS_CONTINUED_AS_NEW'), + ).toBe('ContinuedAsNew'); + }); + it('should return TimedOut from ExecutionStatus enum', () => { + expect( + toWorkflowStatusReadable('WORKFLOW_EXECUTION_STATUS_TIMED_OUT'), + ).toBe('TimedOut'); + }); + it('should return Unspecified from ExecutionStatus enum', () => { + expect( + toWorkflowStatusReadable('WORKFLOW_EXECUTION_STATUS_UNSPECIFIED'), + ).toBe('Unspecified'); + }); + it('should return old Running enum from ExecutionStatus enum', () => { + expect(toWorkflowStatusReadable('Running')).toBe('Running'); + }); +}); + +describe('toNamespaceArchivalStateReadable', () => { + it('should return Enabled from ArchivalState enum', () => { + expect(toNamespaceArchivalStateReadable('ARCHIVAL_STATE_ENABLED')).toBe( + 'Enabled', + ); + }); + it('should return Disabled from ArchivalState enum', () => { + expect(toNamespaceArchivalStateReadable('ARCHIVAL_STATE_DISABLED')).toBe( + 'Disabled', + ); + }); + it('should return Unspecified from ArchivalState enum', () => { + expect(toNamespaceArchivalStateReadable('ARCHIVAL_STATE_UNSPECIFIED')).toBe( + 'Unspecified', + ); + }); + it('should return Disabled from old ArchivalState enum', () => { + expect(toNamespaceArchivalStateReadable('Disabled')).toBe('Disabled'); + }); +}); + +describe('toNamespaceStateReadable', () => { + it('should return Registered from NamespaceState enum', () => { + expect(toNamespaceStateReadable('NAMESPACE_STATE_REGISTERED')).toBe( + 'Registered', + ); + }); + it('should return Deprecated from NamespaceState enum', () => { + expect(toNamespaceStateReadable('NAMESPACE_STATE_DEPRECATED')).toBe( + 'Deprecated', + ); + }); + it('should return Deleted from NamespaceState enum', () => { + expect(toNamespaceStateReadable('NAMESPACE_STATE_DELETED')).toBe('Deleted'); + }); + it('should return Unspecified from NamespaceState enum', () => { + expect(toNamespaceStateReadable('NAMESPACE_STATE_UNSPECIFIED')).toBe( + 'Unspecified', + ); + }); + it('should return Disabled from old NamespaceState enum', () => { + expect(toNamespaceStateReadable('Registered')).toBe('Registered'); + }); +}); + +describe('toBatchOperationStateReadable', () => { + it('should return Running from BatchOperationState enum', () => { + expect(toBatchOperationStateReadable('BATCH_OPERATION_STATE_RUNNING')).toBe( + 'Running', + ); + }); + it('should return Completed from BatchOperationState enum', () => { + expect( + toBatchOperationStateReadable('BATCH_OPERATION_STATE_COMPLETED'), + ).toBe('Completed'); + }); + it('should return Failed from BatchOperationState enum', () => { + expect(toBatchOperationStateReadable('BATCH_OPERATION_STATE_FAILED')).toBe( + 'Failed', + ); + }); + it('should return Unspecified from BatchOperationState enum', () => { + expect( + toBatchOperationStateReadable('BATCH_OPERATION_STATE_UNSPECIFIED'), + ).toBe('Unspecified'); + }); + it('should return Completed from old BatchOperationState enum', () => { + expect(toBatchOperationStateReadable('Completed')).toBe('Completed'); + }); +}); + +describe('toBatchOperationTypeReadable', () => { + it('should return Terminate from BatchOperationType enum', () => { + expect(toBatchOperationTypeReadable('BATCH_OPERATION_TYPE_TERMINATE')).toBe( + 'Terminate', + ); + }); + it('should return Cancel from BatchOperationType enum', () => { + expect(toBatchOperationTypeReadable('BATCH_OPERATION_TYPE_CANCEL')).toBe( + 'Cancel', + ); + }); + it('should return Signal from BatchOperationType enum', () => { + expect(toBatchOperationTypeReadable('BATCH_OPERATION_TYPE_SIGNAL')).toBe( + 'Signal', + ); + }); + it('should return Delete from BatchOperationType enum', () => { + expect(toBatchOperationTypeReadable('BATCH_OPERATION_TYPE_DELETE')).toBe( + 'Delete', + ); + }); + it('should return Reset from BatchOperationType enum', () => { + expect(toBatchOperationTypeReadable('BATCH_OPERATION_TYPE_RESET')).toBe( + 'Reset', + ); + }); + it('should return Unspecified from BatchOperationType enum', () => { + expect( + toBatchOperationTypeReadable('BATCH_OPERATION_TYPE_UNSPECIFIED'), + ).toBe('Unspecified'); + }); + it('should return Signal from old BatchOperationType enum', () => { + expect(toBatchOperationTypeReadable('Signal')).toBe('Signal'); + }); +}); + +describe('toEventNameReadable', () => { + it('should return TimerStarted from EventType enum', () => { + expect(toEventNameReadable('EVENT_TYPE_TIMER_STARTED' as EventType)).toBe( + 'TimerStarted', + ); + }); + it('should return WorkflowExecutionFailed from EventType enum', () => { + expect( + toEventNameReadable('EVENT_TYPE_WORKFLOW_EXECUTION_FAILED' as EventType), + ).toBe('WorkflowExecutionFailed'); + }); + it('should return WorkflowExecutionUpdateRequested from EventType enum', () => { + expect( + toEventNameReadable( + 'EVENT_TYPE_WORKFLOW_EXECUTION_UPDATE_REQUESTED' as EventType, + ), + ).toBe('WorkflowExecutionUpdateRequested'); + }); + it('should return old TimerStarted enum from EventType enum', () => { + expect(toEventNameReadable('TimerStarted')).toBe('TimerStarted'); + }); +}); + +describe('toWorkflowTaskFailureReadable', () => { + it('should return UnhandledCommand enum from WorkflowTaskFailedCause enum', () => { + expect( + toWorkflowTaskFailureReadable( + 'WORKFLOW_TASK_FAILED_CAUSE_UNHANDLED_COMMAND', + ), + ).toBe('UnhandledCommand'); + }); + it('should return Unspecified enum if no cause', () => { + expect(toWorkflowTaskFailureReadable()).toBe('Unspecified'); + }); +}); diff --git a/src/lib/utilities/screaming-enums.ts b/src/lib/utilities/screaming-enums.ts new file mode 100644 index 000000000..48b442f1f --- /dev/null +++ b/src/lib/utilities/screaming-enums.ts @@ -0,0 +1,100 @@ +import type { + ArchivalState, + CallbackState, + NamespaceState, + PendingNexusOperationState, + WorkflowExecutionStatus, +} from '$lib/types'; +import type { BatchOperationState, BatchOperationType } from '$lib/types/batch'; +import type { PendingActivityState } from '$lib/types/events'; +import type { + SearchAttributeType, + WorkflowStatus, + WorkflowTaskFailedCause, +} from '$lib/types/workflows'; + +import type { EventType } from './is-event-type'; + +export const fromScreamingEnum = ( + potentialScreamingEnum: T, + prefix: string, +): T => { + if (!potentialScreamingEnum) return potentialScreamingEnum; + const stringEnum = String(potentialScreamingEnum); + const split = stringEnum.split('_'); + if (!split || split.length <= 1) return potentialScreamingEnum; + const formatted = split + .map((word) => { + return word.charAt(0) + word.substring(1).toLowerCase(); + }) + .join(''); + return formatted.replace(prefix, '') as T; +}; + +export const toSearchAttributeTypeReadable = ( + status: SearchAttributeType, +): SearchAttributeType => { + return fromScreamingEnum(status, 'IndexedValueType'); +}; + +export const toWorkflowStatusReadable = ( + status: WorkflowExecutionStatus | WorkflowStatus, +): WorkflowStatus => { + return fromScreamingEnum(status, 'WorkflowExecutionStatus') as WorkflowStatus; +}; + +export const toNamespaceArchivalStateReadable = ( + status: ArchivalState, +): ArchivalState => { + return fromScreamingEnum(status, 'ArchivalState'); +}; + +export const toNamespaceStateReadable = ( + status: NamespaceState, +): NamespaceState => { + return fromScreamingEnum(status, 'NamespaceState'); +}; + +export const toEventNameReadable = (status: EventType): EventType => { + return fromScreamingEnum(status, 'EventType'); +}; + +export const toBatchOperationStateReadable = ( + status: BatchOperationState, +): BatchOperationState => { + return fromScreamingEnum(status, 'BatchOperationState'); +}; + +export const toBatchOperationTypeReadable = ( + status: BatchOperationType, +): BatchOperationType => { + return fromScreamingEnum(status, 'BatchOperationType'); +}; + +export const toWorkflowTaskFailureReadable = ( + cause?: WorkflowTaskFailedCause, +): WorkflowTaskFailedCause => { + if (!cause) return 'Unspecified'; + return fromScreamingEnum(cause, 'WorkflowTaskFailedCause'); +}; + +export const toPendingActivityStateReadable = ( + state?: PendingActivityState, +): PendingActivityState => { + if (!state) return state; + return fromScreamingEnum(state, 'PendingActivityState'); +}; + +export const toPendingNexusOperationStateReadable = ( + state?: PendingNexusOperationState, +): PendingNexusOperationState => { + if (!state) return state; + return fromScreamingEnum(state, 'PendingNexusOperationState'); +}; + +export const toCallbackStateReadable = ( + state?: CallbackState, +): CallbackState => { + if (!state) return state; + return fromScreamingEnum(state, 'CallbackState'); +}; diff --git a/src/lib/utilities/search-type-parameter.test.ts b/src/lib/utilities/search-type-parameter.test.ts index a22a9900c..c86ae4714 100644 --- a/src/lib/utilities/search-type-parameter.test.ts +++ b/src/lib/utilities/search-type-parameter.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { isValidSearchType, getSearchType } from './search-type-parameter'; + +import { getSearchType, isValidSearchType } from './search-type-parameter'; const invalidInputs = { number: 24, @@ -17,7 +18,7 @@ describe('isValidSearchType', () => { }); } - it(`should return false if given a string that is not "basic" or "advanced"`, () => { + it('should return false if given a string that is not "basic" or "advanced"', () => { expect(isValidSearchType('bogus')).toBe(false); }); }); diff --git a/src/lib/utilities/stack-trace/get-file-paths-from-go-stack-trace.test.ts b/src/lib/utilities/stack-trace/get-file-paths-from-go-stack-trace.test.ts index c9cb0bd1b..dd99d3ff4 100644 --- a/src/lib/utilities/stack-trace/get-file-paths-from-go-stack-trace.test.ts +++ b/src/lib/utilities/stack-trace/get-file-paths-from-go-stack-trace.test.ts @@ -1,9 +1,10 @@ import { describe, expect, it } from 'vitest'; + import { getFilePathsFromGoStackTrace } from './get-file-paths-from-go-stack-trace'; import UnixGoStacktraceQueryJson from '../../../fixtures/stacktrace-query.go-sdk.json'; import WindowsGoStacktraceQueryJson from '../../../fixtures/stacktrace-query.go-sdk.windows.json'; -import TypeScriptStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.windows.json'; import JavaStacktraceQueryJson from '../../../fixtures/stacktrace-query.java-sdk.json'; +import TypeScriptStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.windows.json'; describe('getFilePathsFromGoStackTrace', () => { it('should return properly parsed paths as JSON objects for a stacktrace generated by Go SDK in a UNIX system', () => { diff --git a/src/lib/utilities/stack-trace/get-file-paths-from-go-stack-trace.ts b/src/lib/utilities/stack-trace/get-file-paths-from-go-stack-trace.ts index 7a2215ca4..0a72e8f7d 100644 --- a/src/lib/utilities/stack-trace/get-file-paths-from-go-stack-trace.ts +++ b/src/lib/utilities/stack-trace/get-file-paths-from-go-stack-trace.ts @@ -4,8 +4,8 @@ if the stack trace originates from the Go SDK It returns undefined otherwise */ -import { parseWithBigInt } from '../parse-with-big-int'; import { isFromGoSDK } from './is-from-go-sdk'; +import { parseWithBigInt } from '../parse-with-big-int'; export const getFilePathsFromGoStackTrace = ( stackTraceText: string, diff --git a/src/lib/utilities/stack-trace/get-file-paths-from-stack-trace.test.ts b/src/lib/utilities/stack-trace/get-file-paths-from-stack-trace.test.ts index 59fc44e84..dd8631c7e 100644 --- a/src/lib/utilities/stack-trace/get-file-paths-from-stack-trace.test.ts +++ b/src/lib/utilities/stack-trace/get-file-paths-from-stack-trace.test.ts @@ -1,10 +1,11 @@ import { describe, expect, it } from 'vitest'; -import UnixTSStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.json'; -import WindowsTSStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.windows.json'; + +import { getFilePathsFromStackTrace } from './get-file-paths-from-stack-trace'; import UnixGoStacktraceQueryJson from '../../../fixtures/stacktrace-query.go-sdk.json'; import WindowsGoStacktraceQueryJson from '../../../fixtures/stacktrace-query.go-sdk.windows.json'; import JavaStacktraceQueryJson from '../../../fixtures/stacktrace-query.java-sdk.json'; -import { getFilePathsFromStackTrace } from './get-file-paths-from-stack-trace'; +import UnixTSStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.json'; +import WindowsTSStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.windows.json'; describe('getFilePathsFromStackTrace', () => { it('should return properly parsed paths as JSON objects for a stacktrace generated by TypeScipt SDK in a UNIX system', () => { diff --git a/src/lib/utilities/stack-trace/get-file-paths-from-stack-trace.ts b/src/lib/utilities/stack-trace/get-file-paths-from-stack-trace.ts index 911fadd33..7a653e03c 100644 --- a/src/lib/utilities/stack-trace/get-file-paths-from-stack-trace.ts +++ b/src/lib/utilities/stack-trace/get-file-paths-from-stack-trace.ts @@ -1,7 +1,7 @@ +import { getFilePathsFromGoStackTrace } from './get-file-paths-from-go-stack-trace'; +import { getFilePathsFromTypeScriptStackTrace } from './get-file-paths-from-typescript-stack-trace'; import { isFromGoSDK } from './is-from-go-sdk'; import { isFromTypeScriptSDK } from './is-from-typescript-sdk'; -import { getFilePathsFromTypeScriptStackTrace } from './get-file-paths-from-typescript-stack-trace'; -import { getFilePathsFromGoStackTrace } from './get-file-paths-from-go-stack-trace'; export const getFilePathsFromStackTrace = ( stackTraceText: string, diff --git a/src/lib/utilities/stack-trace/get-file-paths-from-typescript-stack-trace.test.ts b/src/lib/utilities/stack-trace/get-file-paths-from-typescript-stack-trace.test.ts index 2b924df1a..f850509fd 100644 --- a/src/lib/utilities/stack-trace/get-file-paths-from-typescript-stack-trace.test.ts +++ b/src/lib/utilities/stack-trace/get-file-paths-from-typescript-stack-trace.test.ts @@ -1,9 +1,10 @@ import { describe, expect, it } from 'vitest'; + import { getFilePathsFromTypeScriptStackTrace } from './get-file-paths-from-typescript-stack-trace'; -import UnixTSStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.json'; -import WindowsTSStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.windows.json'; import GoStacktraceQueryJson from '../../../fixtures/stacktrace-query.go-sdk.json'; import JavaStacktraceQueryJson from '../../../fixtures/stacktrace-query.java-sdk.json'; +import UnixTSStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.json'; +import WindowsTSStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.windows.json'; describe('getFilePathsFromTypeScriptStackTrace', () => { it('should return properly parsed paths as JSON objects for a stacktrace generated by TypeScipt SDK in a UNIX system', () => { diff --git a/src/lib/utilities/stack-trace/get-file-paths-from-typescript-stack-trace.ts b/src/lib/utilities/stack-trace/get-file-paths-from-typescript-stack-trace.ts index 42a90e07a..57f0b9537 100644 --- a/src/lib/utilities/stack-trace/get-file-paths-from-typescript-stack-trace.ts +++ b/src/lib/utilities/stack-trace/get-file-paths-from-typescript-stack-trace.ts @@ -4,8 +4,8 @@ if the stack trace originates from the TypeScript SDK It returns undefined otherwise */ -import { parseWithBigInt } from '../parse-with-big-int'; import { isFromTypeScriptSDK } from './is-from-typescript-sdk'; +import { parseWithBigInt } from '../parse-with-big-int'; export const getFilePathsFromTypeScriptStackTrace = ( stackTraceText: string, diff --git a/src/lib/utilities/stack-trace/get-sdk-origin.test.ts b/src/lib/utilities/stack-trace/get-sdk-origin.test.ts index 9c6565c3d..5aa2cfd95 100644 --- a/src/lib/utilities/stack-trace/get-sdk-origin.test.ts +++ b/src/lib/utilities/stack-trace/get-sdk-origin.test.ts @@ -1,8 +1,9 @@ import { describe, expect, it } from 'vitest'; + import { getSDKOrigin } from './get-sdk-origin'; -import typeScriptStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.json'; import GoStacktraceQueryJson from '../../../fixtures/stacktrace-query.go-sdk.json'; import JavaStacktraceQueryJson from '../../../fixtures/stacktrace-query.java-sdk.json'; +import typeScriptStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.json'; describe('getSDKOrigin', () => { it('should return "typescript" for a stacktrace generated by TypeScipt SDK', () => { diff --git a/src/lib/utilities/stack-trace/get-sdk-origin.ts b/src/lib/utilities/stack-trace/get-sdk-origin.ts index 283d2eeda..c32f273e6 100644 --- a/src/lib/utilities/stack-trace/get-sdk-origin.ts +++ b/src/lib/utilities/stack-trace/get-sdk-origin.ts @@ -1,9 +1,9 @@ // The function takes a decoded StackTraceQuery response as a string // and returns the name of the SDK the response came from -import { isFromTypeScriptSDK } from './is-from-typescript-sdk'; import { isFromGoSDK } from './is-from-go-sdk'; import { isFromJavaSDK } from './is-from-java-sdk'; +import { isFromTypeScriptSDK } from './is-from-typescript-sdk'; export const getSDKOrigin = (stackTraceText: string): string => { if (isFromTypeScriptSDK(stackTraceText)) return 'typescript'; diff --git a/src/lib/utilities/stack-trace/is-from-go-sdk.test.ts b/src/lib/utilities/stack-trace/is-from-go-sdk.test.ts index 5ec89f11a..7e1f8fd60 100644 --- a/src/lib/utilities/stack-trace/is-from-go-sdk.test.ts +++ b/src/lib/utilities/stack-trace/is-from-go-sdk.test.ts @@ -1,9 +1,10 @@ import { describe, expect, it } from 'vitest'; + import { isFromGoSDK } from './is-from-go-sdk'; -import typeScriptStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.json'; import UnixGoStacktraceQueryJson from '../../../fixtures/stacktrace-query.go-sdk.json'; import WindowsGoStacktraceQueryJson from '../../../fixtures/stacktrace-query.go-sdk.windows.json'; import JavaStacktraceQueryJson from '../../../fixtures/stacktrace-query.java-sdk.json'; +import typeScriptStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.json'; describe('isFromGoSDK', () => { it('should return false for a stacktrace generated by TypeScipt SDK', () => { diff --git a/src/lib/utilities/stack-trace/is-from-java-sdk.test.ts b/src/lib/utilities/stack-trace/is-from-java-sdk.test.ts index 7730c3675..5cdc44bcd 100644 --- a/src/lib/utilities/stack-trace/is-from-java-sdk.test.ts +++ b/src/lib/utilities/stack-trace/is-from-java-sdk.test.ts @@ -1,9 +1,10 @@ import { describe, expect, it } from 'vitest'; + import { isFromJavaSDK } from './is-from-java-sdk'; -import typeScriptStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.json'; import GoStacktraceQueryJson from '../../../fixtures/stacktrace-query.go-sdk.json'; import UnixJavaStacktraceQueryJson from '../../../fixtures/stacktrace-query.java-sdk.json'; import WindowsJavaStacktraceQueryJson from '../../../fixtures/stacktrace-query.java-sdk.windows.json'; +import typeScriptStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.json'; import { stringifyWithBigInt } from '../parse-with-big-int'; describe('isFromJavaSDK', () => { diff --git a/src/lib/utilities/stack-trace/is-from-typescript-sdk.test.ts b/src/lib/utilities/stack-trace/is-from-typescript-sdk.test.ts index 2b6fe04ac..9526c214f 100644 --- a/src/lib/utilities/stack-trace/is-from-typescript-sdk.test.ts +++ b/src/lib/utilities/stack-trace/is-from-typescript-sdk.test.ts @@ -1,9 +1,10 @@ import { describe, expect, it } from 'vitest'; + import { isFromTypeScriptSDK } from './is-from-typescript-sdk'; -import UnixTypeScriptStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.json'; -import WindowsTypeScriptStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.windows.json'; import GoStacktraceQueryJson from '../../../fixtures/stacktrace-query.go-sdk.json'; import JavaStacktraceQueryJson from '../../../fixtures/stacktrace-query.java-sdk.json'; +import UnixTypeScriptStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.json'; +import WindowsTypeScriptStacktraceQueryJson from '../../../fixtures/stacktrace-query.ts-sdk.windows.json'; describe('isFromTypeScripSDK', () => { it('should return true for a stacktrace generated by TypeScipt SDK on a Unix System', () => { diff --git a/src/lib/utilities/stores/with-loading.ts b/src/lib/utilities/stores/with-loading.ts deleted file mode 100644 index 0f3a653be..000000000 --- a/src/lib/utilities/stores/with-loading.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Writable } from 'svelte/store'; - -export const delay = 300; - -export const withLoading = async ( - loading: Writable, - updating: Writable, - fn: () => Promise, -): Promise => { - updating.set(true); - try { - await fn(); - } catch (error) { - console.error(error); - } - loading.set(false); - setTimeout(() => { - updating.set(false); - }, delay); -}; diff --git a/src/lib/utilities/task-queue-compatibility.test.ts b/src/lib/utilities/task-queue-compatibility.test.ts new file mode 100644 index 000000000..1bc915ca0 --- /dev/null +++ b/src/lib/utilities/task-queue-compatibility.test.ts @@ -0,0 +1,560 @@ +import { describe, expect, it } from 'vitest'; + +import { toWorkflowExecution } from '$lib/models/workflow-execution'; + +import { + getCurrentCompatibilityDefaultVersion, + getCurrentWorkflowBuildId, + getDefaultVersionForSet, + getDefaultVersionForSetFromABuildId, + getNonDefaultVersionsForSet, + getOrderedVersionSets, + getUniqueBuildIdsFromPollers, + pollerHasVersioning, + workflowIsCompatibleWithWorkers, +} from './task-queue-compatibility'; + +describe('getOrderedVersionSets', () => { + it('should return empty array if undefined majorVersionSets', () => { + expect(getOrderedVersionSets(undefined)).toStrictEqual([]); + }); + + it('should return empty array if empty array of majorVersionSets', () => { + expect(getOrderedVersionSets([])).toStrictEqual([]); + }); + + it('should return reversed array of majorVersionSets', () => { + const majorVersionSets = [ + { + versionSetId: '1.0', + buildIds: ['1.0', '1.1', '1.2'], + }, + { + versionSetId: '2.0', + buildIds: ['2.0', '2.1', '2.2'], + }, + { + versionSetId: '3.0', + buildIds: ['3.0'], + }, + ]; + expect(getOrderedVersionSets({ majorVersionSets })).toStrictEqual([ + { + versionSetId: '3.0', + buildIds: ['3.0'], + }, + { + versionSetId: '2.0', + buildIds: ['2.0', '2.1', '2.2'], + }, + { + versionSetId: '1.0', + buildIds: ['1.0', '1.1', '1.2'], + }, + ]); + }); +}); + +describe('getDefaultVersionForSet', () => { + it('should return undefined if no buildIds', () => { + expect(getDefaultVersionForSet(undefined)).toBe(undefined); + }); + + it('should return undefined if empty array of buildIds', () => { + expect(getDefaultVersionForSet([])).toBe(undefined); + }); + + it('should return last element of single element array of buildIds', () => { + expect(getDefaultVersionForSet(['1.0'])).toBe('1.0'); + }); + + it('should return last element of array of buildIds', () => { + expect(getDefaultVersionForSet(['1.0', '2.3', '3.6', '4.4'])).toBe('4.4'); + }); +}); + +describe('getDefaultVersionForSet', () => { + it('should return undefined if no buildIds', () => { + expect(getDefaultVersionForSet(undefined)).toBe(undefined); + }); + + it('should return undefined if empty array of buildIds', () => { + expect(getDefaultVersionForSet([])).toBe(undefined); + }); + + it('should return last element of single element array of buildIds', () => { + expect(getDefaultVersionForSet(['1.0'])).toBe('1.0'); + }); + + it('should return last element of array of buildIds', () => { + expect(getDefaultVersionForSet(['1.0', '2.3', '3.6', '4.4'])).toBe('4.4'); + }); +}); + +describe('getNonDefaultVersionsForSet', () => { + it('should return undefined if no buildIds', () => { + expect(getNonDefaultVersionsForSet(undefined)).toStrictEqual([]); + }); + + it('should return undefined if empty array of buildIds', () => { + expect(getNonDefaultVersionsForSet([])).toStrictEqual([]); + }); + + it('should return last element of single element array of buildIds', () => { + expect(getNonDefaultVersionsForSet(['1.0'])).toStrictEqual([]); + }); + + it('should return all but last element of array of buildIds', () => { + expect( + getNonDefaultVersionsForSet(['1.0', '2.3', '3.6', '4.4']), + ).toStrictEqual(['3.6', '2.3', '1.0']); + }); +}); + +describe('getCurrentCompatibilityDefaultVersion', () => { + it('should return undefined if no majorVersionSets', () => { + expect( + getCurrentCompatibilityDefaultVersion({ majorVersionSets: undefined }), + ).toBe(undefined); + }); + + it('should return undefined if empty array of majorVersionSets', () => { + expect( + getCurrentCompatibilityDefaultVersion({ majorVersionSets: [] }), + ).toBe(undefined); + }); + + it('should return default version if one array of majorVersionSets', () => { + const majorVersionSets = [ + { + versionSetId: '1.0', + buildIds: ['1.0', '1.1', '1.2'], + }, + ]; + + expect(getCurrentCompatibilityDefaultVersion({ majorVersionSets })).toBe( + '1.2', + ); + }); + it('should return default version if multiple arrays of majorVersionSets', () => { + const majorVersionSets = [ + { + versionSetId: '1.0', + buildIds: ['1.0', '1.1', '1.2'], + }, + { + versionSetId: '2.0', + buildIds: ['2.0', '2.1', '2.2'], + }, + { + versionSetId: '3.0', + buildIds: ['3.0', '3.1', '3.2'], + }, + ]; + expect(getCurrentCompatibilityDefaultVersion({ majorVersionSets })).toBe( + '3.2', + ); + }); +}); + +const getWorkflow = (mostRecentWorkerVersionStamp) => + toWorkflowExecution({ + executionConfig: { + taskQueue: { + name: 'hello-world', + kind: 'Normal', + normalName: '', + }, + workflowExecutionTimeout: '0s', + workflowRunTimeout: '0s', + defaultWorkflowTaskTimeout: '10s', + }, + workflowExecutionInfo: { + execution: { + workflowId: 'hello_world_workflowID', + runId: '959f8c0a-3af8-4f4b-833a-55d5117cf3de', + }, + type: { + name: 'Workflow', + }, + startTime: '2023-07-13T14:29:25.409300Z', + closeTime: '2023-07-13T14:29:25.422152Z', + status: 'Completed', + historyLength: '11', + parentNamespaceId: '', + parentExecution: null, + executionTime: '2023-07-13T14:29:25.409300Z', + memo: { + fields: {}, + }, + searchAttributes: { + indexedFields: { + BuildIds: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZExpc3Q=', + }, + data: 'WyJ1bnZlcnNpb25lZCIsInVudmVyc2lvbmVkOjAwZDY0ZTAyZTA2NWI1YTEyYmVkMTkyNjVkODZmZDA1Il0=', + }, + }, + }, + autoResetPoints: { + points: [ + { + binaryChecksum: '00d64e02e065b5a12bed19265d86fd05', + runId: '959f8c0a-3af8-4f4b-833a-55d5117cf3de', + firstWorkflowTaskCompletedId: '4', + createTime: '2023-07-13T14:29:25.416779Z', + expireTime: null, + resettable: true, + }, + ], + }, + taskQueue: 'hello-world', + stateTransitionCount: '7', + historySizeBytes: '1237', + mostRecentWorkerVersionStamp: mostRecentWorkerVersionStamp, + }, + pendingActivities: [], + pendingChildren: [], + pendingWorkflowTask: null, + }); + +describe('getCurrentWorkflowBuildId', () => { + it('should return undefined if no worker version stamp', () => { + toWorkflowExecution; + expect(getCurrentWorkflowBuildId(getWorkflow(undefined))).toBe(undefined); + }); + + it('should return undefined if not using versioning', () => { + const mostRecentWorkerVersionStamp = { + buildId: '00d64e02e065b5a12bed19265d86fd05', + bundleId: '', + useVersioning: false, + }; + expect( + getCurrentWorkflowBuildId(getWorkflow(mostRecentWorkerVersionStamp)), + ).toBe(undefined); + }); + + it('should return undefined if not using versioning', () => { + const mostRecentWorkerVersionStamp = { + buildId: '3.5', + bundleId: '', + useVersioning: true, + }; + expect( + getCurrentWorkflowBuildId(getWorkflow(mostRecentWorkerVersionStamp)), + ).toBe('3.5'); + }); +}); + +describe('getDefaultVersionForSetFromABuildId', () => { + it('should return default version from a non-default build id', () => { + const majorVersionSets = [ + { + versionSetId: '1.0', + buildIds: ['1.0', '1.1', '1.2'], + }, + { + versionSetId: '2.0', + buildIds: ['2.0', '2.1', '2.2'], + }, + { + versionSetId: '3.0', + buildIds: ['3.0', '3.1', '3.2'], + }, + ]; + expect( + getDefaultVersionForSetFromABuildId({ majorVersionSets }, '3.0'), + ).toBe('3.2'); + }); + + it('should return default version from a build id', () => { + const majorVersionSets = [ + { + versionSetId: '1.0', + buildIds: ['1.0', '1.1', '1.2'], + }, + { + versionSetId: '2.0', + buildIds: ['2.0', '2.1', '2.2', '2.3'], + }, + { + versionSetId: '3.0', + buildIds: ['3.0', '3.1', '3.2'], + }, + ]; + expect( + getDefaultVersionForSetFromABuildId({ majorVersionSets }, '2.3'), + ).toBe('2.3'); + }); + it('should return undefined if buildId not found', () => { + const majorVersionSets = [ + { + versionSetId: '1.0', + buildIds: ['1.0', '1.1', '1.2'], + }, + { + versionSetId: '2.0', + buildIds: ['2.0', '2.1', '2.2', '2.3'], + }, + { + versionSetId: '3.0', + buildIds: ['3.0', '3.1', '3.2'], + }, + ]; + expect( + getDefaultVersionForSetFromABuildId({ majorVersionSets }, '6.7'), + ).toBe(undefined); + }); +}); + +describe('getUniqueBuildIdsFromPollers', () => { + it('should return single buildId from pollers with one not using versioning', () => { + const pollers = [ + { + lastAccessTime: '2023-08-08T17:31:47.475536Z', + identity: '49105@Test.local@', + ratePerSecond: 100000, + workerVersionCapabilities: { + buildId: '1.1', + useVersioning: true, + }, + }, + { + lastAccessTime: '2023-08-08T17:29:53.396211Z', + identity: '48804@Test.local@', + ratePerSecond: 100000, + workerVersionCapabilities: { + buildId: '382de1cb0b7d4cc19436ef156c3aa973', + useVersioning: false, + }, + }, + ]; + + expect(getUniqueBuildIdsFromPollers(pollers)).toStrictEqual(['1.1']); + }); + + it('should return list of buildIds from pollers with all using versioning', () => { + const pollers = [ + { + lastAccessTime: '2023-08-08T17:31:47.475536Z', + identity: '49105@Test.local@', + ratePerSecond: 100000, + workerVersionCapabilities: { + buildId: '1.1', + useVersioning: true, + }, + }, + { + lastAccessTime: '2023-08-08T17:29:53.396211Z', + identity: '48804@Test.local@', + ratePerSecond: 100000, + workerVersionCapabilities: { + buildId: '3.0', + useVersioning: true, + }, + }, + ]; + + expect(getUniqueBuildIdsFromPollers(pollers)).toStrictEqual(['1.1', '3.0']); + }); + + it('should return unique list of buildIds from pollers with duplicate versioning', () => { + const pollers = [ + { + lastAccessTime: '2023-08-08T17:31:47.475536Z', + identity: '49105@Test.local@', + ratePerSecond: 100000, + workerVersionCapabilities: { + buildId: '1.1', + useVersioning: true, + }, + }, + { + lastAccessTime: '2023-08-08T17:29:53.396211Z', + identity: '48804@Test.local@', + ratePerSecond: 100000, + workerVersionCapabilities: { + buildId: '1.1', + useVersioning: true, + }, + }, + ]; + + expect(getUniqueBuildIdsFromPollers(pollers)).toStrictEqual(['1.1']); + }); +}); + +describe('pollerHasVersioning', () => { + it('should return false from pollers not using versioning', () => { + const pollers = [ + { + lastAccessTime: '2023-08-08T17:31:47.475536Z', + identity: '49105@Test.local@', + ratePerSecond: 100000, + workerVersionCapabilities: { + buildId: '', + useVersioning: false, + }, + }, + { + lastAccessTime: '2023-08-08T17:29:53.396211Z', + identity: '48804@Test.local@', + ratePerSecond: 100000, + workerVersionCapabilities: { + buildId: '', + useVersioning: false, + }, + }, + ]; + + expect(pollerHasVersioning(pollers)).toBe(false); + }); + it('should return true from pollers with one using versioning', () => { + const pollers = [ + { + lastAccessTime: '2023-08-08T17:31:47.475536Z', + identity: '49105@Test.local@', + ratePerSecond: 100000, + workerVersionCapabilities: { + buildId: '1.1', + useVersioning: true, + }, + }, + { + lastAccessTime: '2023-08-08T17:29:53.396211Z', + identity: '48804@Test.local@', + ratePerSecond: 100000, + workerVersionCapabilities: { + buildId: '382de1cb0b7d4cc19436ef156c3aa973', + useVersioning: false, + }, + }, + ]; + + expect(pollerHasVersioning(pollers)).toBe(true); + }); +}); + +describe('workflowIsCompatibleWithWorkers', () => { + const majorVersionSets = [ + { + versionSetId: '1.0', + buildIds: ['1.0', '1.1', '1.2'], + }, + { + versionSetId: '2.0', + buildIds: ['2.0', '2.1', '2.2'], + }, + ]; + + it('should return true if workflow not using versioning', () => { + const pollers = [ + { + lastAccessTime: '2023-08-08T17:31:47.475536Z', + identity: '49105@Test.local@', + ratePerSecond: 100000, + workerVersionCapabilities: { + buildId: '1.2', + useVersioning: true, + }, + }, + { + lastAccessTime: '2023-08-08T17:29:53.396211Z', + identity: '48804@Test.local@', + ratePerSecond: 100000, + workerVersionCapabilities: { + buildId: '2.0', + useVersioning: true, + }, + }, + ]; + const mostRecentWorkerVersionStamp = { + buildId: 'asdfasdfasf0', + bundleId: '', + useVersioning: false, + }; + + expect( + workflowIsCompatibleWithWorkers( + getWorkflow(mostRecentWorkerVersionStamp), + pollers, + { majorVersionSets }, + ), + ).toBe(true); + }); + + it('should return true if pollers have default version of workflow version', () => { + const pollers = [ + { + lastAccessTime: '2023-08-08T17:31:47.475536Z', + identity: '49105@Test.local@', + ratePerSecond: 100000, + workerVersionCapabilities: { + buildId: '1.2', + useVersioning: true, + }, + }, + { + lastAccessTime: '2023-08-08T17:29:53.396211Z', + identity: '48804@Test.local@', + ratePerSecond: 100000, + workerVersionCapabilities: { + buildId: '2.0', + useVersioning: true, + }, + }, + ]; + const mostRecentWorkerVersionStamp = { + buildId: '1.0', + bundleId: '', + useVersioning: true, + }; + + expect( + workflowIsCompatibleWithWorkers( + getWorkflow(mostRecentWorkerVersionStamp), + pollers, + { majorVersionSets }, + ), + ).toBe(true); + }); + + it('should return false if pollers dont have default version of workflow version', () => { + const pollers = [ + { + lastAccessTime: '2023-08-08T17:31:47.475536Z', + identity: '49105@Test.local@', + ratePerSecond: 100000, + workerVersionCapabilities: { + buildId: '1.1', + useVersioning: true, + }, + }, + { + lastAccessTime: '2023-08-08T17:29:53.396211Z', + identity: '48804@Test.local@', + ratePerSecond: 100000, + workerVersionCapabilities: { + buildId: '2.2', + useVersioning: true, + }, + }, + ]; + const mostRecentWorkerVersionStamp = { + buildId: '1.0', + bundleId: '', + useVersioning: true, + }; + + expect( + workflowIsCompatibleWithWorkers( + getWorkflow(mostRecentWorkerVersionStamp), + pollers, + { majorVersionSets }, + ), + ).toBe(false); + }); +}); diff --git a/src/lib/utilities/task-queue-compatibility.ts b/src/lib/utilities/task-queue-compatibility.ts new file mode 100644 index 000000000..0486f973a --- /dev/null +++ b/src/lib/utilities/task-queue-compatibility.ts @@ -0,0 +1,100 @@ +import type { + PollerWithTaskQueueTypes, + TaskQueueCompatibility, +} from '$lib/services/pollers-service'; +import type { PollerInfo, TaskQueueCompatibleVersionSet } from '$lib/types'; +import type { WorkflowExecution } from '$lib/types/workflows'; + +export const getOrderedVersionSets = ( + compatibility: TaskQueueCompatibility | undefined, +): TaskQueueCompatibleVersionSet[] => { + const sets = compatibility?.majorVersionSets + ? [...compatibility.majorVersionSets] + : []; + return sets.reverse(); +}; + +export const getDefaultVersionForSet = ( + buildIds?: string[], +): string | undefined => { + if (!buildIds || !buildIds.length) return undefined; + return buildIds[buildIds.length - 1]; +}; + +export const getDefaultVersionForSetFromABuildId = ( + compatibility: TaskQueueCompatibility | undefined, + buildId: string, +): string | undefined => { + const buildIdSet = compatibility?.majorVersionSets?.find((set) => + set.buildIds?.includes(buildId), + ); + return getDefaultVersionForSet(buildIdSet?.buildIds); +}; + +export const getNonDefaultVersionsForSet = (buildIds?: string[]): string[] => { + if (!buildIds) return []; + return buildIds.slice(0, buildIds.length - 1).reverse(); +}; + +export const getCurrentCompatibilityDefaultVersion = ( + compatibility: TaskQueueCompatibility | undefined, +): string | undefined => { + const orderedSets = getOrderedVersionSets(compatibility); + if (!orderedSets.length) return undefined; + + const defaultVersionBuildIds = orderedSets[0]?.buildIds; + if (!defaultVersionBuildIds?.length) return undefined; + + return getDefaultVersionForSet(orderedSets[0].buildIds); +}; + +export const getCurrentWorkflowBuildId = ( + workflow: WorkflowExecution, +): string | undefined => { + if (workflow?.assignedBuildId) return workflow.assignedBuildId; + if (workflow?.mostRecentWorkerVersionStamp?.useVersioning) { + return workflow.mostRecentWorkerVersionStamp?.buildId || undefined; + } + return undefined; +}; + +export const getCurrentPollerBuildId = ( + poller: PollerInfo, +): string | undefined => { + if (poller?.workerVersionCapabilities?.useVersioning) { + return poller.workerVersionCapabilities?.buildId || undefined; + } + return undefined; +}; + +export const getUniqueBuildIdsFromPollers = ( + pollers: PollerWithTaskQueueTypes[], +): string[] => { + const buildIds = [ + ...new Set(pollers?.map(getCurrentPollerBuildId).filter(Boolean)), + ]; + return buildIds; +}; + +export const pollerHasVersioning = ( + pollers: PollerWithTaskQueueTypes[], +): boolean => { + return pollers?.some( + (poller) => poller?.workerVersionCapabilities?.useVersioning, + ); +}; + +export const workflowIsCompatibleWithWorkers = ( + workflow: WorkflowExecution, + pollers: PollerWithTaskQueueTypes[], + compatibility: TaskQueueCompatibility | undefined, +): boolean => { + const workflowBuildId = getCurrentWorkflowBuildId(workflow); + if (!workflowBuildId) return true; + const defaultVersionForBuildId = getDefaultVersionForSetFromABuildId( + compatibility, + workflowBuildId, + ); + const buildIds = getUniqueBuildIdsFromPollers(pollers); + return buildIds.includes(defaultVersionForBuildId); +}; diff --git a/src/lib/utilities/to-duration.test.ts b/src/lib/utilities/to-duration.test.ts index 35a16e3fd..917e22974 100644 --- a/src/lib/utilities/to-duration.test.ts +++ b/src/lib/utilities/to-duration.test.ts @@ -1,15 +1,17 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + import { - toDuration, - toDate, + durations, fromDate, - isDurationString, - isDurationKey, - toString, - isDuration, fromSeconds, + isDuration, + isDurationKey, + isDurationString, + isValidDurationQuery, + toDate, + toDuration, tomorrow, - durations, + toString, } from './to-duration'; describe('toDuration', () => { @@ -101,14 +103,14 @@ describe('toDate', () => { }); it('should produce a date based on a duration', () => { - const ninetyDaysEarlier = '2019-10-03T00:00:00Z'; + const ninetyDaysEarlier = '2019-10-03T00:00:00.000Z'; const result = toDate({ days: 90 }); expect(result).toBe(ninetyDaysEarlier); }); it('should produce a date based on a duration string', () => { - const ninetyDaysEarlier = '2019-10-03T00:00:00Z'; + const ninetyDaysEarlier = '2019-10-03T00:00:00.000Z'; const result = toDate('90 days'); expect(result).toBe(ninetyDaysEarlier); @@ -126,11 +128,11 @@ describe('tomorrow', () => { }); it('should create a date 24 hours in the future', () => { - expect(tomorrow()).toBe('2020-01-02T00:00:00Z'); + expect(tomorrow()).toBe('2020-01-02T00:00:00.000Z'); }); it('should create a date 24 hours in the future from an argument', () => { - expect(tomorrow(new Date('2022-07-01'))).toBe('2022-07-02T00:00:00Z'); + expect(tomorrow(new Date('2022-07-01'))).toBe('2022-07-02T00:00:00.000Z'); }); }); @@ -340,28 +342,62 @@ describe('toString', () => { }); describe('fromSeconds', () => { - it('should return undefined if given invalid input', () => { - expect(fromSeconds('lolol')).toBeUndefined(); + it('should return an empty string given a string that does not end in seconds', () => { + expect(fromSeconds('3600m')).toEqual(''); }); - it('should return undefined given a string that does not end in seconds', () => { - expect(fromSeconds('3600m')).toBeUndefined(); + it('should correctly format milliseconds', () => { + expect(fromSeconds('0.001s')).toEqual('1 millisecond'); + expect(fromSeconds('0.006s')).toEqual('6 milliseconds'); + expect(fromSeconds('0.6s')).toEqual('600 milliseconds'); + expect(fromSeconds('0.06s')).toEqual('60 milliseconds'); + expect(fromSeconds('0.0006s')).toEqual('0.6 milliseconds'); + expect(fromSeconds('0.00006s')).toEqual('0.06 milliseconds'); + expect(fromSeconds('0.000000001s')).toEqual('0.000001 milliseconds'); + expect(fromSeconds('0.000000006s')).toEqual('0.000006 milliseconds'); }); - it('should correctly parse minutes', () => { - expect(fromSeconds('60s')).toMatchObject({ minutes: 1 }); + it('should correctly format seconds', () => { + expect(fromSeconds('0s')).toEqual(''); + expect(fromSeconds('0.00s')).toEqual(''); + expect(fromSeconds('1s')).toEqual('1 second'); + expect(fromSeconds('6s')).toEqual('6 seconds'); + expect(fromSeconds('6.00s')).toEqual('6 seconds'); + expect(fromSeconds('6.412382134s')).toEqual('6 seconds, 412 milliseconds'); + expect(fromSeconds('59.32322s')).toEqual('59 seconds, 323 milliseconds'); }); - it('should correctly parse hours', () => { - expect(fromSeconds('3600s')).toMatchObject({ hours: 1 }); + it('should correctly format minutes', () => { + expect(fromSeconds('60s')).toEqual('1 minute'); + expect(fromSeconds('61.1234123412s')).toEqual('1 minute, 1 second'); }); - it('should correctly parse minutes', () => { - expect(fromSeconds('3660s')).toMatchObject({ hours: 1, minutes: 1 }); + it('should correctly format hours', () => { + expect(fromSeconds('3600s')).toEqual('1 hour'); }); - it('should return undefined if given bogus input', () => { - expect(fromSeconds('bogus')).toBeUndefined(); + it('should correctly format hours and minutes', () => { + expect(fromSeconds('3660s')).toEqual('1 hour, 1 minute'); + }); + + it('should correctly format hours, minutes and seconds', () => { + expect(fromSeconds('3661s')).toEqual('1 hour, 1 minute, 1 second'); + expect(fromSeconds('3666s')).toEqual('1 hour, 1 minute, 6 seconds'); + }); + + it('should correctly format hours, minutes, seconds and milliseconds', () => { + expect(fromSeconds('3661s')).toEqual('1 hour, 1 minute, 1 second'); + expect(fromSeconds('3661.06s')).toEqual('1 hour, 1 minute, 1 second'); + expect(fromSeconds('3661.006s')).toEqual('1 hour, 1 minute, 1 second'); + expect(fromSeconds('2148128.1234123412s')).toEqual( + '24 days, 20 hours, 42 minutes, 8 seconds', + ); + }); + + it('should return an empty string if given bogus input', () => { + expect(fromSeconds('bogus')).toEqual(''); + expect(fromSeconds('bogus.01')).toEqual(''); + expect(fromSeconds('10.bogus')).toEqual(''); }); }); @@ -381,3 +417,63 @@ describe('durations', () => { `); }); }); + +describe('isValidDurationQuery', () => { + it('should return true if the value is a valid number (for nanoseconds)', () => { + expect(isValidDurationQuery('01')).toBe(true); + expect(isValidDurationQuery('-01')).toBe(true); + expect(isValidDurationQuery('1')).toBe(true); + expect(isValidDurationQuery('-1')).toBe(true); + expect(isValidDurationQuery('10')).toBe(true); + expect(isValidDurationQuery('-10')).toBe(true); + }); + + it('should return false if the value is not a valid number (for nanoseconds)', () => { + expect(isValidDurationQuery('test')).toBe(false); + expect(isValidDurationQuery('1e6t')).toBe(false); + }); + + it('should return true if the value is in a valid HH:MM:SS format', () => { + expect(isValidDurationQuery('00')).toBe(true); + expect(isValidDurationQuery('00:00:00')).toBe(true); + expect(isValidDurationQuery('0:00:00')).toBe(true); + expect(isValidDurationQuery('01:00:00')).toBe(true); + expect(isValidDurationQuery('90:00:00')).toBe(true); + expect(isValidDurationQuery('10000:00:00')).toBe(true); + expect(isValidDurationQuery('00:01:00')).toBe(true); + expect(isValidDurationQuery('00:59:00')).toBe(true); + expect(isValidDurationQuery('00:00:01')).toBe(true); + expect(isValidDurationQuery('00:00:59')).toBe(true); + }); + + it('should return false if the value is not in a valid HH:MM:SS format', () => { + expect(isValidDurationQuery('00:00')).toBe(false); + expect(isValidDurationQuery('00:60:00')).toBe(false); + expect(isValidDurationQuery('00:00:60')).toBe(false); + }); + + it('should return true if the value is in valid Golang duration format', () => { + expect(isValidDurationQuery('1000ns')).toBe(true); + expect(isValidDurationQuery('-1000ns')).toBe(true); + expect(isValidDurationQuery('2us')).toBe(true); + expect(isValidDurationQuery('-2us')).toBe(true); + expect(isValidDurationQuery('3µs')).toBe(true); + expect(isValidDurationQuery('-3µs')).toBe(true); + expect(isValidDurationQuery('4ms')).toBe(true); + expect(isValidDurationQuery('-4ms')).toBe(true); + expect(isValidDurationQuery('5s')).toBe(true); + expect(isValidDurationQuery('-5s')).toBe(true); + expect(isValidDurationQuery('60m')).toBe(true); + expect(isValidDurationQuery('-60m')).toBe(true); + expect(isValidDurationQuery('700h')).toBe(true); + expect(isValidDurationQuery('-700h')).toBe(true); + expect(isValidDurationQuery('2h30m')).toBe(true); + }); + + it('should return false if the value is not in a valid Golang duration format', () => { + expect(isValidDurationQuery('1 hour')).toBe(false); + expect(isValidDurationQuery('60min')).toBe(false); + expect(isValidDurationQuery('10 s')).toBe(false); + expect(isValidDurationQuery(' 100 ms')).toBe(false); + }); +}); diff --git a/src/lib/utilities/to-duration.ts b/src/lib/utilities/to-duration.ts index 6915c343f..66fd38348 100644 --- a/src/lib/utilities/to-duration.ts +++ b/src/lib/utilities/to-duration.ts @@ -1,5 +1,13 @@ -import { formatISO, sub, add, intervalToDuration, parseISO } from 'date-fns'; -import { isString, isObject } from './is'; +import { + add, + formatDuration as durationToString, + intervalToDuration, + parseISO, + sub, +} from 'date-fns'; + +import { isObject, isString } from './is'; +import { pluralize } from './pluralize'; type DurationKey = keyof Duration; @@ -67,13 +75,15 @@ export const isDurationString = (value: unknown): value is string => { }; export const tomorrow = (date = new Date()): string => { - return formatISO(add(date, { hours: 24 })); + return add(date, { hours: 24 }).toISOString(); }; export const toDuration = (value: string): Duration => { const duration: Duration = {}; const segments = value.match(durationPattern); + if (!segments) return duration; + for (const segment of segments) { const [amount, unit] = segment.split(' '); const n = parseInt(amount, 10); @@ -90,20 +100,21 @@ export const toDuration = (value: string): Duration => { return duration; }; -export const toString = (duration: Duration): string => { - let units = Object.keys(duration)[0]; - const amount: number = duration[units]; - - if (amount === 1) units = units.slice(0, units.length - 1); +const singularlize = (unit: string, amount: number): string => { + if (amount === 1) return unit.slice(0, unit.length - 1); + return unit; +}; - return `${amount} ${units}`; +export const toString = (duration: Duration): string => { + const [[units, amount]] = Object.entries(duration); + return `${amount} ${singularlize(units, amount)}`; }; export const toDate = (timeRange: Duration | string): string => { const duration = typeof timeRange === 'string' ? toDuration(timeRange) : timeRange; - return formatISO(sub(new Date(), duration)); + return sub(new Date(), duration).toISOString(); }; export const fromDate = ( @@ -116,11 +127,44 @@ export const fromDate = ( return intervalToDuration({ start, end }); }; -export const fromSeconds = (seconds: string): Duration => { - const milliseconds = parseInt(seconds) * 1000; +export const fromSeconds = ( + seconds: string, + { delimiter = ', ' } = {}, +): string => { + const parsedSeconds = parseInt(seconds); + const parsedDecimal = parseFloat(`.${seconds.split('.')[1] ?? 0}`); + + if (!seconds.endsWith('s')) return ''; + if (isNaN(parsedSeconds) || isNaN(parsedDecimal)) return ''; + + const start = new Date(Date.UTC(0, 0, 0, 0, 0, 0)); + const end = new Date(Date.UTC(0, 0, 0, 0, 0, parsedSeconds)); + const duration = intervalToDuration({ start, end }); + const durationString = durationToString(duration, { delimiter }); + const milliseconds = + Math.round(parsedDecimal * 1000 * 1000000000) / 1000000000; // round to nanoseconds + + if (!milliseconds) return durationString; + if (!durationString) + return `${milliseconds} ${pluralize('millisecond', milliseconds)}`; + if (parsedSeconds < 60) { + return `${durationString}, ${milliseconds.toFixed(0)} ${pluralize( + 'millisecond', + milliseconds, + )}`; + } + + return `${durationString}`; +}; - if (!seconds.endsWith('s')) return undefined; - if (isNaN(milliseconds)) return undefined; +export const isValidDurationQuery = (value: string): boolean => { + const isValidNumber = !isNaN(Number(value)); + const isValidGolangDuration = + /^-?\d+\S*$/.test(value) && + ['ns', 'us', 'µs', 'ms', 's', 'm', 'h'].some((char) => + value.endsWith(char), + ); + const isValidTime = /^\d+:[0-5][0-9]:[0-5][0-9]$/.test(value); - return intervalToDuration({ start: 0, end: milliseconds }); + return isValidNumber || isValidGolangDuration || isValidTime; }; diff --git a/src/lib/utilities/to-time-difference.test.ts b/src/lib/utilities/to-time-difference.test.ts new file mode 100644 index 000000000..3387bfbc6 --- /dev/null +++ b/src/lib/utilities/to-time-difference.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from 'vitest'; + +import { toTimeDifference } from './to-time-difference'; + +describe('toTimeDifference', () => { + const negativeDefault = 'None'; + + it('should correctly parse a Timestamp', () => { + const now = 1649867374630; + const date = '2022-04-13T16:29:35.630571Z'; + expect(toTimeDifference({ date, now, negativeDefault })).toEqual('1s'); + }); + + it('should correctly parse a negative difference', () => { + const now = 1683060815883; + const date = '2022-04-13T16:29:35.630571Z'; + expect(toTimeDifference({ date, now })).toEqual('-33193440s'); + }); + + it('should correctly parse a negative difference with a default', () => { + const now = 1683060815883; + const date = '2022-04-13T16:29:35.630571Z'; + expect(toTimeDifference({ date, now, negativeDefault })).toEqual('None'); + expect(toTimeDifference({ date, now, negativeDefault: '' })).toEqual(''); + }); + + it('should correctly parse an invalid date', () => { + expect(toTimeDifference({ date: null })).toEqual(''); + expect(toTimeDifference({ date: '' })).toEqual(''); + expect(toTimeDifference({ date: undefined })).toEqual(''); + expect(toTimeDifference({ date: 'date' })).toEqual(''); + }); +}); diff --git a/src/lib/utilities/to-time-difference.ts b/src/lib/utilities/to-time-difference.ts index 0d7c17b21..b72be881b 100644 --- a/src/lib/utilities/to-time-difference.ts +++ b/src/lib/utilities/to-time-difference.ts @@ -1,11 +1,26 @@ -export const toTimeDifference = (date: unknown, now = Date.now()): string => { +import type { Timestamp } from '$lib/types'; + +export const toTimeDifference = ({ + date, + now = Date.now(), + negativeDefault, +}: { + date: Timestamp; + now?: number; + negativeDefault?: string; +}): string => { if (!date) return ''; const start = String(date); try { const scheduled = Number(new Date(start)); const timeFromNow = (scheduled - now) / 1000; - return !isNaN(timeFromNow) ? `${timeFromNow}s` : ''; + + if (negativeDefault !== undefined && timeFromNow < 0) { + return negativeDefault; + } + + return !isNaN(timeFromNow) ? `${timeFromNow.toFixed(0)}s` : ''; } catch (error) { return ''; } diff --git a/src/lib/utilities/to-url.test.ts b/src/lib/utilities/to-url.test.ts index 70cbd1b75..cf6db8a34 100644 --- a/src/lib/utilities/to-url.test.ts +++ b/src/lib/utilities/to-url.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { toURL } from './to-url'; describe('toURL', () => { @@ -6,6 +7,10 @@ describe('toURL', () => { expect(toURL('/workflows')).toBe('/workflows'); }); + it('should take a string and hash for the URL and return it', () => { + expect(toURL('/workflows', undefined, '123')).toBe('/workflows#123'); + }); + it('should turn the query params into a query string', () => { const params = new URLSearchParams({ a: 'hello' }); expect(toURL('/workflows', params)).toBe('/workflows?a=hello'); @@ -15,4 +20,14 @@ describe('toURL', () => { const params = { a: 'hello' }; expect(toURL('/workflows', params)).toBe('/workflows?a=hello'); }); + + it('should turn an object into a query string and include it with a hash', () => { + const params = { a: 'hello' }; + expect(toURL('/workflows', params, '123')).toBe('/workflows?a=hello#123'); + }); + + it('should turn the query params into a query string with a hash', () => { + const params = new URLSearchParams({ a: 'hello' }); + expect(toURL('/workflows', params, '1')).toBe('/workflows?a=hello#1'); + }); }); diff --git a/src/lib/utilities/to-url.ts b/src/lib/utilities/to-url.ts index 986584fb5..77e9cf195 100644 --- a/src/lib/utilities/to-url.ts +++ b/src/lib/utilities/to-url.ts @@ -1,9 +1,11 @@ export const toURL = ( url: string, params?: URLSearchParams | Record, + hash?: string, ): string => { const isURLSearchParams = params instanceof URLSearchParams; if (params && !isURLSearchParams) params = new URLSearchParams(params); - if (params) return `${url}?${params}`; + if (params) url = `${url}?${params}`; + if (hash) url = `${url}#${hash}`; return url; }; diff --git a/src/lib/utilities/trim-trailing-slash.test.ts b/src/lib/utilities/trim-trailing-slash.test.ts new file mode 100644 index 000000000..f848fa94c --- /dev/null +++ b/src/lib/utilities/trim-trailing-slash.test.ts @@ -0,0 +1,12 @@ +import { describe, expect, it } from 'vitest'; + +import { trimTrailingSlash } from './trim-trailing-slash'; + +describe('trimTrailingSlash', () => { + it('should remove trailing slash from a string', () => { + expect(trimTrailingSlash('http://cats.meow/')).toEqual('http://cats.meow'); + }); + it('should return original string if no trailing slash', () => { + expect(trimTrailingSlash('http://cats.meow')).toEqual('http://cats.meow'); + }); +}); diff --git a/src/lib/utilities/trim-trailing-slash.ts b/src/lib/utilities/trim-trailing-slash.ts new file mode 100644 index 000000000..163ae909c --- /dev/null +++ b/src/lib/utilities/trim-trailing-slash.ts @@ -0,0 +1,3 @@ +export const trimTrailingSlash = (x: string): string => { + return x.replace(/\/+$/, ''); +}; diff --git a/src/lib/utilities/unique.test.ts b/src/lib/utilities/unique.test.ts index 666b7a243..121a3ecf3 100644 --- a/src/lib/utilities/unique.test.ts +++ b/src/lib/utilities/unique.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { unique } from './unique'; describe('unique', () => { diff --git a/src/lib/utilities/update-query-parameters.test.ts b/src/lib/utilities/update-query-parameters.test.ts index 9c3c6d603..bffb168a8 100644 --- a/src/lib/utilities/update-query-parameters.test.ts +++ b/src/lib/utilities/update-query-parameters.test.ts @@ -1,130 +1,231 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; -import { addHashToURL, updateQueryParameters } from './update-query-parameters'; - -const gotoOptions = { replaceState: true, keepfocus: true, noscroll: true }; -const url = new URL('https://temporal.io'); +import { + gotoOptions, + updateMultipleQueryParameters, + updateQueryParameters, +} from './update-query-parameters'; describe('updateQueryParameters', () => { afterEach(() => { vi.clearAllMocks(); }); - it('should call the set method on the query when a value is provided', () => { + it('should call `goto` with the correct path when no value is provided', () => { + const url = new URL('https://temporal.io'); const parameter = 'parameter'; - const value = 'value'; - const goto = () => Promise.resolve(); + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); - const spy = vi.spyOn(url.searchParams, 'set'); + updateQueryParameters({ parameter, url, goto }); - updateQueryParameters({ parameter, value, url, goto }); + const [href, options] = goto.mock.calls[0]; - expect(spy).toHaveBeenCalled(); - expect(spy).toHaveBeenCalledWith(parameter, value); + expect(href).toBe('/'); + expect(options).toEqual(gotoOptions); }); - it('should call the delete method on the query when no value is provided', () => { + it('should call `goto` with the correct path when an empty string is provided', () => { + const url = new URL('https://temporal.io'); const parameter = 'parameter'; - const goto = () => Promise.resolve(); + const value = ''; + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); - const spy = vi.spyOn(url.searchParams, 'delete'); + updateQueryParameters({ parameter, value, url, goto }); - updateQueryParameters({ parameter, url, goto }); + const [href, options] = goto.mock.calls[0]; - expect(spy).toHaveBeenCalled(); - expect(spy).toHaveBeenCalledWith(parameter); + expect(href).toBe('/'); + expect(options).toEqual(gotoOptions); }); - it('should call the delete method on the query when an empty string is provided', () => { + it('should call `goto` with the correct path when an emmpty string is provided and there are other params', () => { + const url = new URL('https://temporal.io/?other=value'); const parameter = 'parameter'; const value = ''; - const goto = () => Promise.resolve(); - const spy = vi.spyOn(url.searchParams, 'delete'); + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateQueryParameters({ parameter, value, url, goto }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/?other=value'); + expect(options).toEqual(gotoOptions); + }); + + it('should call `goto` with the correct path when null is provided', () => { + const url = new URL('https://temporal.io'); + const parameter = 'parameter'; + const value = null as unknown as string; + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); updateQueryParameters({ parameter, value, url, goto }); - expect(spy).toHaveBeenCalled(); - expect(spy).toHaveBeenCalledWith(parameter); + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/'); + expect(options).toEqual(gotoOptions); }); - it('should call the delete method on the query when null is provided', () => { + it('should call `goto` with the correct path when null is provided and there are other params', () => { + const url = new URL('https://temporal.io/?other=value'); const parameter = 'parameter'; const value = null as unknown as string; - const goto = () => Promise.resolve(); - const spy = vi.spyOn(url.searchParams, 'delete'); + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); updateQueryParameters({ parameter, value, url, goto }); - expect(spy).toHaveBeenCalled(); - expect(spy).toHaveBeenCalledWith(parameter); + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/?other=value'); + expect(options).toEqual(gotoOptions); }); it('should call `goto` with the correct path', () => { + const url = new URL('https://temporal.io'); const parameter = 'parameter'; const value = 'value'; const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); updateQueryParameters({ parameter, value, url, goto }); - expect(goto).toHaveBeenCalledWith( - 'https://temporal.io/?parameter=value#', - gotoOptions, - ); + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/?parameter=value'); + expect(options).toEqual(gotoOptions); + }); + + it('should call `goto` with the correct path when there are other params', () => { + const url = new URL('https://temporal.io/?other=value'); + const parameter = 'parameter'; + const value = 'value'; + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateQueryParameters({ parameter, value, url, goto }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/?other=value¶meter=value'); + expect(options).toEqual(gotoOptions); }); it('should call `goto` with the correct path when query parameters already exist', () => { const parameter = 'parameter'; - const value = 'newvalue'; + const value = 'value'; + const url = new URL(`https://temporal.io/?${parameter}=oldvalue`); + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateQueryParameters({ parameter, value, url, goto }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/?parameter=value'); + expect(options).toEqual(gotoOptions); + }); + + it('should call `goto` with the correct path for the updated param when there are other params', () => { + const parameter = 'parameter'; + const value = 'value'; + const url = new URL( + `https://temporal.io/?${parameter}=oldvalue&other=value`, + ); const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); updateQueryParameters({ parameter, value, url, goto }); - expect(goto).toHaveBeenCalledWith( - 'https://temporal.io/?parameter=newvalue#', - gotoOptions, + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/?parameter=value&other=value'); + expect(options).toEqual(gotoOptions); + }); + + it('should call `goto` with the correct path for the updated param and keep order if there are other params', () => { + const parameter = 'parameter'; + const value = 'newValue'; + const url = new URL( + `https://temporal.io/?other1=value1&${parameter}=oldValue&other2=value2`, ); + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateQueryParameters({ parameter, value, url, goto }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/?other1=value1¶meter=newValue&other2=value2'); + expect(options).toEqual(gotoOptions); }); it('should call `goto` with without the "?" if the query params are empty', () => { + const url = new URL('https://temporal.io'); const parameter = 'parameter'; const value = null as unknown as string; const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); updateQueryParameters({ parameter, value, url, goto }); - expect(goto).toHaveBeenCalledWith('https://temporal.io/#', gotoOptions); + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/'); + expect(options).toEqual(gotoOptions); }); it('should set the parameter to an empty string if allowEmpty is set', () => { + const url = new URL('https://temporal.io'); const parameter = 'parameter'; const value = ''; const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); updateQueryParameters({ parameter, value, url, goto, allowEmpty: true }); - expect(goto).toHaveBeenCalledWith( - 'https://temporal.io/?parameter=#', - gotoOptions, - ); - }); -}); + const [href, options] = goto.mock.calls[0]; -describe('addHashToURL', () => { - it('should add a hash to a URL', () => { - const url = new URL('https://temporal.io/'); - expect(addHashToURL(url)).toBe('https://temporal.io/#'); + expect(href).toBe('/?parameter='); + expect(options).toEqual(gotoOptions); }); - it('should not add a duplicate hash to a URL', () => { - const url = new URL('https://temporal.io/#'); - expect(addHashToURL(url)).toBe('https://temporal.io/#'); + it('should clear single clearParameter when on a page', () => { + const parameter = 'parameter'; + const value = 'value'; + const url = new URL( + `https://temporal.io/?${parameter}=oldvalue&other=value`, + ); + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateQueryParameters({ + parameter, + value, + url, + goto, + clearParameters: ['other'], + }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/?parameter=value'); + expect(options).toEqual(gotoOptions); }); - it('should put the hash after the search parameters', () => { - const url = new URL('https://temporal.io/?parameter=value'); - expect(addHashToURL(url)).toBe('https://temporal.io/?parameter=value#'); + it('should clear multiple clearParameters when on a page', () => { + const parameter = 'parameter'; + const value = 'value'; + const url = new URL( + `https://temporal.io/?${parameter}=oldvalue&other=value&page=3`, + ); + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateQueryParameters({ + parameter, + value, + url, + goto, + clearParameters: ['page', 'other'], + }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/?parameter=value'); + expect(options).toEqual(gotoOptions); }); }); @@ -152,3 +253,225 @@ describe('a sanity test for how URLs work in JavaScript', () => { expect(url.hash).toBe('#hash'); }); }); + +describe('updateMultipleQueryParameters', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should call `goto` with the correct path when no value is provided', () => { + const url = new URL('https://temporal.io'); + const parameters = [{ parameter: 'parameter' }]; + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateMultipleQueryParameters({ parameters, url, goto }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/'); + expect(options).toEqual(gotoOptions); + }); + + it('should call `goto` with the correct path when an empty string is provided', () => { + const url = new URL('https://temporal.io'); + const parameters = [{ parameter: 'parameter', value: '' }]; + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateMultipleQueryParameters({ parameters, url, goto }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/'); + expect(options).toEqual(gotoOptions); + }); + + it('should call `goto` with the correct path when an emmpty string is provided and there are other params', () => { + const url = new URL('https://temporal.io/?other=value'); + const parameters = [{ parameter: 'parameter', value: '' }]; + + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateMultipleQueryParameters({ parameters, url, goto }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/?other=value'); + expect(options).toEqual(gotoOptions); + }); + + it('should call `goto` with the correct path when null is provided', () => { + const url = new URL('https://temporal.io'); + const parameters = [ + { parameter: 'parameter', value: null as unknown as string }, + ]; + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateMultipleQueryParameters({ parameters, url, goto }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/'); + expect(options).toEqual(gotoOptions); + }); + + it('should call `goto` with the correct path when null is provided and there are other params', () => { + const url = new URL('https://temporal.io/?other=value'); + const parameters = [ + { parameter: 'parameter', value: null as unknown as string }, + ]; + + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateMultipleQueryParameters({ parameters, url, goto }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/?other=value'); + expect(options).toEqual(gotoOptions); + }); + + it('should call `goto` with the correct path', () => { + const url = new URL('https://temporal.io'); + const parameters = [{ parameter: 'parameter', value: 'value' }]; + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateMultipleQueryParameters({ parameters, url, goto }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/?parameter=value'); + expect(options).toEqual(gotoOptions); + }); + + it('should call `goto` with the correct path if multiple parameters are provided', () => { + const url = new URL('https://temporal.io'); + const parameters = [ + { parameter: 'A', value: 'value' }, + { parameter: 'B', value: 'value' }, + ]; + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateMultipleQueryParameters({ parameters, url, goto }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/?A=value&B=value'); + expect(options).toEqual(gotoOptions); + }); + + it('should call `goto` with the correct path when there are other params', () => { + const url = new URL('https://temporal.io/?other=value'); + const parameters = [{ parameter: 'parameter', value: 'value' }]; + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateMultipleQueryParameters({ parameters, url, goto }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/?other=value¶meter=value'); + expect(options).toEqual(gotoOptions); + }); + + it('should call `goto` with the correct path when there are other params and multiple parameters are provided', () => { + const url = new URL('https://temporal.io/?other=value'); + const parameters = [ + { parameter: 'A', value: 'value' }, + { parameter: 'B', value: 'value' }, + ]; + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateMultipleQueryParameters({ parameters, url, goto }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/?other=value&A=value&B=value'); + expect(options).toEqual(gotoOptions); + }); + + it('should call `goto` with the correct path when query parameters already exist', () => { + const parameter = 'parameter'; + const parameters = [{ parameter, value: 'value' }]; + const url = new URL(`https://temporal.io/?${parameter}=oldvalue`); + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateMultipleQueryParameters({ parameters, url, goto }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/?parameter=value'); + expect(options).toEqual(gotoOptions); + }); + + it('should call `goto` with the correct path for the updated param when there are other params', () => { + const parameter = 'parameter'; + const parameters = [{ parameter, value: 'value' }]; + const url = new URL( + `https://temporal.io/?${parameter}=oldvalue&other=value`, + ); + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateMultipleQueryParameters({ parameters, url, goto }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/?other=value¶meter=value'); + expect(options).toEqual(gotoOptions); + }); + + it('should call `goto` with without the "?" if the query params are empty', () => { + const url = new URL('https://temporal.io'); + const parameters = [ + { parameter: 'parameter', value: null as unknown as string }, + ]; + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateMultipleQueryParameters({ parameters, url, goto }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/'); + expect(options).toEqual(gotoOptions); + }); + + it('should clear single clearParameter when on a page', () => { + const parameter = 'parameter'; + const parameters = [{ parameter, value: 'value' }]; + const url = new URL( + `https://temporal.io/?${parameter}=oldvalue&other=value`, + ); + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateMultipleQueryParameters({ + parameters, + url, + goto, + clearParameters: ['other'], + }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/?parameter=value'); + expect(options).toEqual(gotoOptions); + }); + + it('should clear multiple clearParameters when on a page', () => { + const parameter = 'parameter'; + const parameters = [{ parameter, value: 'value' }]; + const url = new URL( + `https://temporal.io/?${parameter}=oldvalue&other=value&page=3`, + ); + const goto = vi.fn().mockImplementation(() => Promise.resolve(null)); + + updateMultipleQueryParameters({ + parameters, + url, + goto, + clearParameters: ['page', 'other'], + }); + + const [href, options] = goto.mock.calls[0]; + + expect(href).toBe('/?parameter=value'); + expect(options).toEqual(gotoOptions); + }); +}); diff --git a/src/lib/utilities/update-query-parameters.ts b/src/lib/utilities/update-query-parameters.ts index 0796db6ff..4d3b47512 100644 --- a/src/lib/utilities/update-query-parameters.ts +++ b/src/lib/utilities/update-query-parameters.ts @@ -1,6 +1,6 @@ -import { browser } from '$app/env'; +import { BROWSER } from 'esm-env'; + import { goto as navigateTo } from '$app/navigation'; -import type { invalidate } from '$app/navigation'; type UpdateQueryParams = { parameter: string; @@ -8,13 +8,14 @@ type UpdateQueryParams = { url: URL; goto?: typeof navigateTo; allowEmpty?: boolean; - invalidate?: typeof invalidate; + clearParameters?: string[]; + options?: typeof gotoOptions; }; -const gotoOptions = { - replaceState: true, - keepfocus: true, - noscroll: true, +export const gotoOptions = { + keepFocus: true, + noScroll: true, + replaceState: false, }; export const updateQueryParameters = async ({ @@ -23,25 +24,92 @@ export const updateQueryParameters = async ({ url, goto = navigateTo, allowEmpty = false, + clearParameters = [], + options = gotoOptions, }: UpdateQueryParams): Promise => { const next = String(value); + const params = {}; + let replaced = false; + + url.searchParams.forEach((value, key) => { + if (key !== parameter) { + params[key] = value; + } else { + if (next) { + params[key] = next; + replaced = true; + } else if (allowEmpty) { + params[key] = ''; + replaced = true; + } + } + }); + const newQuery = new URLSearchParams(params); + + if (value && !replaced) { + newQuery.set(parameter, next); + } else if (allowEmpty && !replaced) { + newQuery.set(parameter, ''); + } - if (value) { - url.searchParams.set(parameter, next); - } else if (allowEmpty) { - url.searchParams.set(parameter, ''); - } else { - url.searchParams.delete(parameter); + if (clearParameters.length) { + clearParameters.forEach((parameter) => { + newQuery.delete(parameter); + }); } - if (browser && url.href !== window.location.href) { - goto(addHashToURL(url), gotoOptions); + if (BROWSER) { + const query = newQuery?.toString(); + const newUrl = query ? `${url.pathname}?${query}` : url.pathname; + + goto(newUrl, options); } return value; }; -export const addHashToURL = (url: URL): string => { - url.hash = '#'; - return String(url); +export type QueryParameter = { + parameter: string; + value?: string | number | boolean; +}; + +type UpdateMultipleQueryParams = { + parameters: QueryParameter[]; + url: URL; + goto?: typeof navigateTo; + clearParameters?: string[]; +}; + +export const updateMultipleQueryParameters = async ({ + parameters, + url, + goto = navigateTo, + clearParameters = [], +}: UpdateMultipleQueryParams) => { + const params: { [key: string]: string } = {}; + url.searchParams.forEach((value, key) => { + if (!parameters.find(({ parameter }) => parameter === key)) { + params[key] = value; + } + }); + const newQuery = new URLSearchParams(params); + + parameters.forEach(({ parameter, value }) => { + if (value || value === false) { + newQuery.set(parameter, String(value)); + } + }); + + if (clearParameters.length) { + clearParameters.forEach((parameter) => { + newQuery.delete(parameter); + }); + } + + if (BROWSER) { + const query = newQuery?.toString(); + const newUrl = query ? `${url.pathname}?${query}` : url.pathname; + + goto(newUrl, gotoOptions); + } }; diff --git a/src/lib/utilities/version-check.test.ts b/src/lib/utilities/version-check.test.ts index da3614947..a200235ba 100644 --- a/src/lib/utilities/version-check.test.ts +++ b/src/lib/utilities/version-check.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { isVersionNewer } from './version-check'; + +import { isVersionNewer, minimumVersionRequired } from './version-check'; describe('isRecommendedVersionNewer', () => { it('should return true when recommended version is higher than current', () => { @@ -8,6 +9,7 @@ describe('isRecommendedVersionNewer', () => { expect(isVersionNewer('1.01.2', '1.01.1')).toBe(true); expect(isVersionNewer('1.02', '1.01')).toBe(true); expect(isVersionNewer('2', '1')).toBe(true); + expect(isVersionNewer('1.20.0', '1.19')).toBe(true); }); it('should return false when recommended version is not higher than current', () => { @@ -22,5 +24,34 @@ describe('isRecommendedVersionNewer', () => { expect(isVersionNewer('1.1', '1.01.1')).toBe(false); expect(isVersionNewer('1.01', '1.02')).toBe(false); expect(isVersionNewer('1', '2')).toBe(false); + expect(isVersionNewer('1.19.3', '1.19')).toBe(false); + }); +}); + +describe('minimumVersionRequired', () => { + it('should return true when current version is equal to or higher than minimum', () => { + expect(minimumVersionRequired('1.20.0', '1.20.0')).toBe(true); + expect(minimumVersionRequired('1.20.1', '1.20.2')).toBe(true); + expect(minimumVersionRequired('1.20.5', '1.21')).toBe(true); + expect(minimumVersionRequired('1.20.5', '2')).toBe(true); + expect(minimumVersionRequired('1.20', '1.20')).toBe(true); + expect(minimumVersionRequired('1', '1')).toBe(true); + expect(minimumVersionRequired('1', '2')).toBe(true); + expect(minimumVersionRequired('1', '1.20')).toBe(true); + }); + it('should return true when current release candidate version is equal to or higher than minimum', () => { + expect(minimumVersionRequired('1.23', '1.23.0-rc.19')).toBe(true); + }); + + it('should return false when current version is less than than minimum', () => { + expect(minimumVersionRequired('1.20.1', '1.20.0')).toBe(false); + expect(minimumVersionRequired('1.20.0', '1.19.0')).toBe(false); + expect(minimumVersionRequired('1.20.0', '1.19.5')).toBe(false); + expect(minimumVersionRequired('1.20', '1.19.5')).toBe(false); + expect(minimumVersionRequired('1.20', '1.19')).toBe(false); + expect(minimumVersionRequired('2', '1')).toBe(false); + expect(minimumVersionRequired('1.20.0', '1.20')).toBe(false); + expect(minimumVersionRequired('1.20.0', '')).toBe(false); + expect(minimumVersionRequired('', '1.30')).toBe(false); }); }); diff --git a/src/lib/utilities/version-check.ts b/src/lib/utilities/version-check.ts index b3f6a7984..d04cff0fd 100644 --- a/src/lib/utilities/version-check.ts +++ b/src/lib/utilities/version-check.ts @@ -17,3 +17,28 @@ export const isVersionNewer = ( return patch1 > patch2; } }; + +export const minimumVersionRequired = ( + minimumVersion: string, + currentVersion: string, +): boolean => { + if (!minimumVersion || !currentVersion) { + return false; + } + + const [major1, minor1, patch1] = minimumVersion.split('.').map(Number); + const [major2, minor2, patch2] = currentVersion.split('.').map(Number); + + if (major1 !== major2) { + return major2 > major1; + } else if (minor1 !== minor2) { + if (minor1 === undefined && !!minor2) return true; + if (minor1 === undefined && minor2 === undefined) return true; + return minor2 > minor1; + } else { + if (patch1 === undefined && !!patch2) return true; + if (patch1 === undefined && (patch2 === undefined || isNaN(patch2))) + return true; + return patch2 >= patch1; + } +}; diff --git a/src/lib/utilities/workflow-actions.test.ts b/src/lib/utilities/workflow-actions.test.ts new file mode 100644 index 000000000..3cc514a9e --- /dev/null +++ b/src/lib/utilities/workflow-actions.test.ts @@ -0,0 +1,113 @@ +import { describe, expect, it } from 'vitest'; + +import { formatReason, getPlacholder } from './workflow-actions'; +import { Action } from '../models/workflow-actions'; + +describe('getPlacholder', () => { + describe('without an authorized user', () => { + it('should return the correct placeholder', () => { + expect(getPlacholder(Action.Cancel)).toEqual('Canceled from the Web UI'); + expect(getPlacholder(Action.Reset)).toEqual('Reset from the Web UI'); + expect(getPlacholder(Action.Terminate)).toEqual( + 'Terminated from the Web UI', + ); + }); + }); + + describe('with authorized user', () => { + it('should return the correct placeholder', () => { + expect(getPlacholder(Action.Cancel, 'test@temporal.io')).toEqual( + 'Canceled from the Web UI by test@temporal.io', + ); + expect(getPlacholder(Action.Reset, 'test@temporal.io')).toEqual( + 'Reset from the Web UI by test@temporal.io', + ); + expect(getPlacholder(Action.Terminate, 'test@temporal.io')).toEqual( + 'Terminated from the Web UI by test@temporal.io', + ); + }); + }); +}); + +describe('formatReason', () => { + describe('without an authorized user', () => { + it('should return the reason with the placeholder', () => { + expect( + formatReason({ action: Action.Cancel, reason: 'Testing' }), + ).toEqual('Testing Canceled from the Web UI'); + expect(formatReason({ action: Action.Reset, reason: 'Testing' })).toEqual( + 'Testing Reset from the Web UI', + ); + expect( + formatReason({ action: Action.Terminate, reason: 'Testing' }), + ).toEqual('Testing Terminated from the Web UI'); + }); + }); + + it('should return the placeholder if there is no reason', () => { + const reason = ''; + expect(formatReason({ action: Action.Cancel, reason })).toEqual( + 'Canceled from the Web UI', + ); + expect(formatReason({ action: Action.Reset, reason })).toEqual( + 'Reset from the Web UI', + ); + expect(formatReason({ action: Action.Terminate, reason })).toEqual( + 'Terminated from the Web UI', + ); + }); + + describe('with an authorized user', () => { + it('should return the reason with the placeholder', () => { + const reason = 'Testing'; + + expect( + formatReason({ + action: Action.Cancel, + reason, + email: 'test@temporal.io', + }), + ).toEqual('Testing Canceled from the Web UI by test@temporal.io'); + expect( + formatReason({ + action: Action.Reset, + reason, + email: 'test@temporal.io', + }), + ).toEqual('Testing Reset from the Web UI by test@temporal.io'); + expect( + formatReason({ + action: Action.Terminate, + reason, + email: 'test@temporal.io', + }), + ).toEqual('Testing Terminated from the Web UI by test@temporal.io'); + }); + + it('should return the placeholder if there is no reason', () => { + const reason = ''; + + expect( + formatReason({ + action: Action.Cancel, + reason, + email: 'test@temporal.io', + }), + ).toEqual('Canceled from the Web UI by test@temporal.io'); + expect( + formatReason({ + action: Action.Reset, + reason, + email: 'test@temporal.io', + }), + ).toEqual('Reset from the Web UI by test@temporal.io'); + expect( + formatReason({ + action: Action.Terminate, + reason, + email: 'test@temporal.io', + }), + ).toEqual('Terminated from the Web UI by test@temporal.io'); + }); + }); +}); diff --git a/src/lib/utilities/workflow-actions.ts b/src/lib/utilities/workflow-actions.ts new file mode 100644 index 000000000..5fb402a01 --- /dev/null +++ b/src/lib/utilities/workflow-actions.ts @@ -0,0 +1,45 @@ +import { translate } from '$lib/i18n/translate'; +import { Action } from '$lib/models/workflow-actions'; + +function unhandledAction(action: never) { + console.error('Unhandled action:', action); +} + +export const getPlacholder = (action: Action, email?: string): string => { + let translatedAction: string; + switch (action) { + case Action.Cancel: + translatedAction = translate('workflows.canceled'); + break; + case Action.Reset: + translatedAction = translate('workflows.reset'); + break; + case Action.Terminate: + translatedAction = translate('workflows.terminated'); + break; + default: + unhandledAction(action); + } + + return email + ? translate('workflows.workflow-action-reason-placeholder-with-email', { + action: translatedAction, + email, + }) + : translate('workflows.workflow-action-reason-placeholder', { + action: translatedAction, + }); +}; + +export const formatReason = ({ + action, + reason, + email, +}: { + action: Action; + reason: string; + email?: string; +}) => { + const placeholder = getPlacholder(action, email); + return reason ? [reason.trim(), placeholder].join(' ') : placeholder; +}; diff --git a/src/lib/utilities/workflow-cancel-enabled.test.ts b/src/lib/utilities/workflow-cancel-enabled.test.ts new file mode 100644 index 000000000..26680f69a --- /dev/null +++ b/src/lib/utilities/workflow-cancel-enabled.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, test } from 'vitest'; + +import { workflowCancelEnabled } from './workflow-cancel-enabled'; + +describe('workflowCancelEnabled', () => { + const coreUser = { + namespaceWriteDisabled: (ns: string) => ns === 'ns-write-disabled', + }; + + test('returns true when global write actions, cancel, and namespace write are all enabled', () => { + expect( + workflowCancelEnabled( + { + disableWriteActions: false, + worklowCancelDisabled: false, + }, + coreUser, + 'ns-write-enabled', + ), + ).toBe(true); + }); + + describe('returns false', () => { + test('when write actions are disabled', () => { + expect( + workflowCancelEnabled( + { + disableWriteActions: true, + workflowCancelDisabled: false, + }, + coreUser, + 'ns-write-enabled', + ), + ).toBe(false); + }); + + test('when cancel is disabled', () => { + expect( + workflowCancelEnabled( + { + disableWriteActions: false, + workflowCancelDisabled: true, + }, + coreUser, + 'ns-write-enabled', + ), + ).toBe(false); + }); + + test('when write actions and cancel are both disabled', () => { + expect( + workflowCancelEnabled( + { + disableWriteActions: true, + workflowCancelDisabled: true, + }, + coreUser, + 'ns-write-enabled', + ), + ).toBe(false); + }); + + test('when write actions are disabled on the namespace', () => { + expect( + workflowCancelEnabled( + { + disableWriteActions: false, + workflowCancelDisabled: false, + }, + coreUser, + 'ns-write-disabled', + ), + ).toBe(false); + }); + }); +}); diff --git a/src/lib/utilities/workflow-cancel-enabled.ts b/src/lib/utilities/workflow-cancel-enabled.ts new file mode 100644 index 000000000..1c84af972 --- /dev/null +++ b/src/lib/utilities/workflow-cancel-enabled.ts @@ -0,0 +1,14 @@ +import type { CoreUser } from '$lib/models/core-user'; +import type { Settings } from '$lib/types/global'; + +export const workflowCancelEnabled = ( + settings: Settings, + coreUser: CoreUser, + namespace: string, +): boolean => { + return ( + !settings.disableWriteActions && + !settings.workflowCancelDisabled && + !coreUser.namespaceWriteDisabled(namespace) + ); +}; diff --git a/src/lib/utilities/workflow-create-disabled.ts b/src/lib/utilities/workflow-create-disabled.ts new file mode 100644 index 000000000..116e75a2f --- /dev/null +++ b/src/lib/utilities/workflow-create-disabled.ts @@ -0,0 +1,19 @@ +import { get } from 'svelte/store'; + +import type { Page } from '@sveltejs/kit'; + +import { coreUserStore } from '$lib/stores/core-user'; + +export const workflowCreateDisabled = ( + page: Page, + namespace?: string, +): boolean => { + const coreUser = coreUserStore(); + const namespaceWriteDisabled = get(coreUser).namespaceWriteDisabled( + namespace ?? page.params.namespace, + ); + if (page?.data?.settings?.disableWriteActions) return true; + if (page?.data?.settings?.startWorkflowDisabled) return true; + + return namespaceWriteDisabled; +}; diff --git a/src/lib/utilities/workflow-reset-enabled.test.ts b/src/lib/utilities/workflow-reset-enabled.test.ts new file mode 100644 index 000000000..2827e6988 --- /dev/null +++ b/src/lib/utilities/workflow-reset-enabled.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, test } from 'vitest'; + +import { workflowResetEnabled } from './workflow-reset-enabled'; + +describe('workflowResetEnabled', () => { + const coreUser = { + namespaceWriteDisabled: (ns: string) => ns === 'ns-write-disabled', + }; + + test('returns true when global write actions, reset, and namespace write actions are all enabled', () => { + expect( + workflowResetEnabled( + { + disableWriteActions: false, + worklowResetDisabled: false, + }, + coreUser, + 'ns-write-enabled', + ), + ).toBe(true); + }); + + describe('returns false', () => { + test('when write actions are disabled', () => { + expect( + workflowResetEnabled( + { + disableWriteActions: true, + workflowResetDisabled: false, + }, + coreUser, + 'ns-write-enabled', + ), + ).toBe(false); + }); + + test('when reset is disabled', () => { + expect( + workflowResetEnabled( + { + disableWriteActions: false, + workflowResetDisabled: true, + }, + coreUser, + 'ns-write-enabled', + ), + ).toBe(false); + }); + + test('when write actions and reset are both disabled', () => { + expect( + workflowResetEnabled( + { + disableWriteActions: true, + workflowResetDisabled: true, + }, + coreUser, + 'ns-write-enabled', + ), + ).toBe(false); + }); + + test('when namespace write actions are disabled', () => { + expect( + workflowResetEnabled( + { + disableWriteActions: false, + workflowResetDisabled: false, + }, + coreUser, + 'ns-write-disabled', + ), + ).toBe(false); + }); + }); +}); diff --git a/src/lib/utilities/workflow-reset-enabled.ts b/src/lib/utilities/workflow-reset-enabled.ts new file mode 100644 index 000000000..606fe9d39 --- /dev/null +++ b/src/lib/utilities/workflow-reset-enabled.ts @@ -0,0 +1,14 @@ +import type { CoreUser } from '$lib/models/core-user'; +import type { Settings } from '$lib/types/global'; + +export const workflowResetEnabled = ( + settings: Settings, + coreUser: CoreUser, + namespace: string, +): boolean => { + return ( + !settings.disableWriteActions && + !settings.workflowResetDisabled && + !coreUser.namespaceWriteDisabled(namespace) + ); +}; diff --git a/src/lib/utilities/workflow-signal-enabled.test.ts b/src/lib/utilities/workflow-signal-enabled.test.ts new file mode 100644 index 000000000..68d6c0150 --- /dev/null +++ b/src/lib/utilities/workflow-signal-enabled.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, test } from 'vitest'; + +import { workflowSignalEnabled } from './workflow-signal-enabled'; + +describe('workflowSignalEnabled', () => { + const coreUser = { + namespaceWriteDisabled: (ns: string) => ns === 'ns-write-disabled', + }; + + test('returns true when global write actions, signal, and namespace write actions are all enabled', () => { + expect( + workflowSignalEnabled( + { + disableWriteActions: false, + worklowCancelDisabled: false, + }, + coreUser, + 'ns-wriete-enabled', + ), + ).toBe(true); + }); + + describe('returns false', () => { + test('when write actions are disabled', () => { + expect( + workflowSignalEnabled( + { + disableWriteActions: true, + workflowCancelDisabled: false, + }, + coreUser, + 'ns-wriete-enabled', + ), + ).toBe(false); + }); + + test('when signal is disabled', () => { + expect( + workflowSignalEnabled( + { + disableWriteActions: false, + workflowSignalDisabled: true, + }, + coreUser, + 'ns-wriete-enabled', + ), + ).toBe(false); + }); + + test('when write actions and signal are both disabled', () => { + expect( + workflowSignalEnabled( + { + disableWriteActions: true, + workflowSignalDisabled: true, + }, + coreUser, + 'ns-wriete-enabled', + ), + ).toBe(false); + }); + + test('when namespace write actions are disabled', () => { + expect( + workflowSignalEnabled( + { + disableWriteActions: false, + workflowSignalDisabled: false, + }, + coreUser, + 'ns-write-disabled', + ), + ).toBe(false); + }); + }); +}); diff --git a/src/lib/utilities/workflow-signal-enabled.ts b/src/lib/utilities/workflow-signal-enabled.ts new file mode 100644 index 000000000..8f8b3e1ed --- /dev/null +++ b/src/lib/utilities/workflow-signal-enabled.ts @@ -0,0 +1,14 @@ +import type { CoreUser } from '$lib/models/core-user'; +import type { Settings } from '$lib/types/global'; + +export const workflowSignalEnabled = ( + settings: Settings, + coreUser: CoreUser, + namespace: string, +): boolean => { + return ( + !settings.disableWriteActions && + !settings.workflowSignalDisabled && + !coreUser.namespaceWriteDisabled(namespace) + ); +}; diff --git a/src/lib/utilities/workflow-terminate-enabled.test.ts b/src/lib/utilities/workflow-terminate-enabled.test.ts new file mode 100644 index 000000000..f69220232 --- /dev/null +++ b/src/lib/utilities/workflow-terminate-enabled.test.ts @@ -0,0 +1,73 @@ +import { describe, expect, test } from 'vitest'; + +import { workflowTerminateEnabled } from './workflow-terminate-enabled'; + +describe('workflowTerminateEnabled', () => { + const coreUser = { + namespaceWriteDisabled: (ns: string) => ns === 'ns-write-disabled', + }; + + test('returns true when global write actions terminate, and namespace write actions are all enabled', () => { + expect( + workflowTerminateEnabled( + { + disableWriteActions: false, + workflowTerminateDisabled: false, + }, + coreUser, + 'ns-write-enabled', + ), + ); + }); + + describe('returns false', () => { + test('when write actions are disabled', () => { + expect( + workflowTerminateEnabled( + { disableWriteActions: true }, + coreUser, + 'ns-write-enabled', + ), + ).toBe(false); + }); + + test('when terminate is disabled', () => { + expect( + workflowTerminateEnabled( + { + disableWriteActions: false, + workflowTerminateDisabled: true, + }, + coreUser, + 'ns-write-enabled', + ), + ).toBe(false); + }); + + test('when write actions and terminate are both disabled', () => { + expect( + workflowTerminateEnabled( + { + disableWriteActions: true, + workflowTerminateDisabled: true, + }, + coreUser, + 'ns-write-enabled', + ), + ).toBe(false); + }); + + test('when namespace write actions are disabled', () => { + expect( + workflowTerminateEnabled( + { + disableWriteActions: false, + workflowTerminateDisabled: false, + }, + coreUser, + 'ns-write-disabled', + ), + ).toBe(false); + }); + }); +}); diff --git a/src/lib/utilities/workflow-terminate-enabled.ts b/src/lib/utilities/workflow-terminate-enabled.ts new file mode 100644 index 000000000..4a0c0bd47 --- /dev/null +++ b/src/lib/utilities/workflow-terminate-enabled.ts @@ -0,0 +1,14 @@ +import type { CoreUser } from '$lib/models/core-user'; +import type { Settings } from '$lib/types/global'; + +export const workflowTerminateEnabled = ( + settings: Settings, + coreUser: CoreUser, + namespace: string, +): boolean => { + return ( + !settings.disableWriteActions && + !settings.workflowTerminateDisabled && + !coreUser.namespaceWriteDisabled(namespace) + ); +}; diff --git a/src/lib/utilities/workflow-update-enabled.test.ts b/src/lib/utilities/workflow-update-enabled.test.ts new file mode 100644 index 000000000..f31734928 --- /dev/null +++ b/src/lib/utilities/workflow-update-enabled.test.ts @@ -0,0 +1,75 @@ +import { describe, expect, test } from 'vitest'; + +import { workflowUpdateEnabled } from './workflow-update-enabled'; + +describe('workflowUpdateEnabled', () => { + const coreUser = { + namespaceWriteDisabled: (ns: string) => ns === 'ns-write-disabled', + }; + + test('returns true when global write actions, signal, and namespace write actions are all enabled', () => { + expect( + workflowUpdateEnabled( + { + disableWriteActions: false, + }, + coreUser, + 'ns-write-enabled', + ), + ).toBe(true); + }); + + describe('returns false', () => { + test('when write actions are disabled', () => { + expect( + workflowUpdateEnabled( + { + disableWriteActions: true, + workflowUpdateDisabled: false, + }, + coreUser, + 'ns-write-enabled', + ), + ).toBe(false); + }); + + test('when update is disabled', () => { + expect( + workflowUpdateEnabled( + { + disableWriteActions: false, + workflowUpdateDisabled: true, + }, + coreUser, + 'ns-write-enabled', + ), + ).toBe(false); + }); + + test('when write actions and update are both disabled', () => { + expect( + workflowUpdateEnabled( + { + disableWriteActions: true, + workflowUpdateDisabled: true, + }, + coreUser, + 'ns-write-enabled', + ), + ).toBe(false); + }); + + test('when namespace write actions are disabled', () => { + expect( + workflowUpdateEnabled( + { + disableWriteActions: false, + workflowUpdateDisabled: false, + }, + coreUser, + 'ns-write-disabled', + ), + ).toBe(false); + }); + }); +}); diff --git a/src/lib/utilities/workflow-update-enabled.ts b/src/lib/utilities/workflow-update-enabled.ts new file mode 100644 index 000000000..7714d940e --- /dev/null +++ b/src/lib/utilities/workflow-update-enabled.ts @@ -0,0 +1,14 @@ +import type { CoreUser } from '$lib/models/core-user'; +import type { Settings } from '$lib/types/global'; + +export const workflowUpdateEnabled = ( + settings: Settings, + coreUser: CoreUser, + namespace: string, +): boolean => { + return ( + !settings.disableWriteActions && + !settings.workflowUpdateDisabled && + !coreUser.namespaceWriteDisabled(namespace) + ); +}; diff --git a/src/lib/utilities/write-actions-are-allowed.test.ts b/src/lib/utilities/write-actions-are-allowed.test.ts index 9c39b1acf..c2542f35a 100644 --- a/src/lib/utilities/write-actions-are-allowed.test.ts +++ b/src/lib/utilities/write-actions-are-allowed.test.ts @@ -1,6 +1,9 @@ import { Writable, writable } from 'svelte/store'; + import { describe, expect, it } from 'vitest'; + import { writeActionsAreAllowed } from './write-actions-are-allowed'; +import type { Settings } from '../../types/global'; describe('writeActionsAreAllowed', () => { it('should return true if the disableWriteActions flag is set to false', () => { diff --git a/src/lib/utilities/write-actions-are-allowed.ts b/src/lib/utilities/write-actions-are-allowed.ts index c249102c1..30a66f94a 100644 --- a/src/lib/utilities/write-actions-are-allowed.ts +++ b/src/lib/utilities/write-actions-are-allowed.ts @@ -1,4 +1,5 @@ import { get } from 'svelte/store'; + import { settings } from '$lib/stores/settings'; export const writeActionsAreAllowed = (store = settings): boolean => { diff --git a/src/lib/vendor/Temporal_Logo_Animation.gif b/src/lib/vendor/Temporal_Logo_Animation.gif index 818d7220e..810dd84d9 100644 Binary files a/src/lib/vendor/Temporal_Logo_Animation.gif and b/src/lib/vendor/Temporal_Logo_Animation.gif differ diff --git a/src/lib/vendor/Temporal_Logo_Animation.mp4 b/src/lib/vendor/Temporal_Logo_Animation.mp4 new file mode 100644 index 000000000..b4a69ad7e Binary files /dev/null and b/src/lib/vendor/Temporal_Logo_Animation.mp4 differ diff --git a/src/lib/vendor/Temporal_Logo_Animation.webm b/src/lib/vendor/Temporal_Logo_Animation.webm new file mode 100644 index 000000000..cb380cf7d Binary files /dev/null and b/src/lib/vendor/Temporal_Logo_Animation.webm differ diff --git a/src/lib/vendor/andromeda.png b/src/lib/vendor/andromeda.png new file mode 100644 index 000000000..ec4328a07 Binary files /dev/null and b/src/lib/vendor/andromeda.png differ diff --git a/src/lib/vendor/codemirror/custom-extensions.ts b/src/lib/vendor/codemirror/custom-extensions.ts new file mode 100644 index 000000000..208640c49 --- /dev/null +++ b/src/lib/vendor/codemirror/custom-extensions.ts @@ -0,0 +1,94 @@ +import { json } from '@codemirror/lang-json'; +import { HighlightStyle, StreamLanguage } from '@codemirror/language'; +import { shell } from '@codemirror/legacy-modes/mode/shell'; +import { EditorView } from '@codemirror/view'; +import { tags } from '@lezer/highlight'; +import colors from 'tailwindcss/colors'; + +import { css } from '$lib/theme/utilities'; + +export type EditorLanguage = 'json' | 'text' | 'shell'; + +export const getEditorTheme = ({ isDark }: { isDark: boolean }) => + EditorView.theme( + { + '&': { + color: css('--color-text-primary'), + backgroundColor: css('--color-surface-code-block'), + borderWidth: '1px', + borderColor: css('--color-border-subtle'), + height: '100%', + }, + '.cm-scroller': { + fontFamily: 'Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace', + padding: '0.5rem', + }, + '.cm-content': { + caretColor: css('--color-text-primary'), + fontSize: '0.875em', + }, + '.cm-editor&.cm-focused': { + outline: `3px solid ${colors.indigo['600']}`, + }, + '.cm-gutters': { + backgroundColor: 'transparent', + borderRight: 'none', + }, + }, + { dark: isDark }, + ); + +export const getActionsTheme = ({ hasActions }: { hasActions: boolean }) => + EditorView.theme({ + '.cm-content': { + ...(hasActions ? { marginRight: '1.75rem' } : {}), + }, + }); + +export const getHeightTheme = ({ + maxHeight, + minHeight, + maximized, +}: { + maxHeight: number; + minHeight: number; + maximized: boolean; +}) => + EditorView.theme({ + '&': { + ...(minHeight && !maximized ? { 'min-height': `${minHeight}px` } : {}), + ...(maxHeight && !maximized ? { 'max-height': `${maxHeight}px` } : {}), + }, + }); + +export const highlightStyles = HighlightStyle.define( + [ + { tag: tags.punctuation, color: css('--color-text-primary') }, + { tag: tags.string, color: css('--color-text-primary') }, + { tag: tags.propertyName, color: css('--color-text-brand') }, + { tag: tags.bool, color: css('--color-text-primary') }, + { tag: tags.number, color: css('--color-text-primary') }, + { tag: tags.operator, color: css('--color-text-pink') }, + { tag: tags.comment, color: css('--color-text-success') }, + { tag: tags.variableName, color: css('--color-text-pink') }, + ], + { themeType: 'light' }, +); + +export const getLineBreakExtension = (editable: boolean) => + EditorView.updateListener.of((update) => { + if (editable) return; + + const newText = update.state.doc.toString().replace(/\\n/g, '\n'); + if (newText !== update.state.doc.toString()) { + update.view.dispatch({ + changes: { from: 0, to: update.state.doc.length, insert: newText }, + }); + } + }); + +export const getLanguageExtension = (language: EditorLanguage) => + ({ + json: json(), + shell: StreamLanguage.define(shell), + })[language] ?? undefined; diff --git a/src/lib/vendor/empty-state-dark_2x.png b/src/lib/vendor/empty-state-dark_2x.png new file mode 100644 index 000000000..50cd6d4cb Binary files /dev/null and b/src/lib/vendor/empty-state-dark_2x.png differ diff --git a/src/lib/vendor/empty-state-dark_4x.png b/src/lib/vendor/empty-state-dark_4x.png new file mode 100644 index 000000000..34defe2bd Binary files /dev/null and b/src/lib/vendor/empty-state-dark_4x.png differ diff --git a/src/lib/vendor/empty-state-light_2x.png b/src/lib/vendor/empty-state-light_2x.png new file mode 100644 index 000000000..fd371bfc3 Binary files /dev/null and b/src/lib/vendor/empty-state-light_2x.png differ diff --git a/src/lib/vendor/empty-state-light_4x.png b/src/lib/vendor/empty-state-light_4x.png new file mode 100644 index 000000000..e0acb7925 Binary files /dev/null and b/src/lib/vendor/empty-state-light_4x.png differ diff --git a/src/lib/vendor/prism/prism.css b/src/lib/vendor/prism/prism.css deleted file mode 100644 index 0a54974da..000000000 --- a/src/lib/vendor/prism/prism.css +++ /dev/null @@ -1,131 +0,0 @@ -/* PrismJS 1.25.0 -https://prismjs.com/download.html#languages=json */ - -code[class*='language-'], -pre[class*='language-'] { - color: #ccc; - background: none; - font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; - font-size: 1em; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - word-wrap: normal; - line-height: 1.5; - - -moz-tab-size: 4; - -o-tab-size: 4; - tab-size: 4; - - -webkit-hyphens: none; - -moz-hyphens: none; - -ms-hyphens: none; - hyphens: none; - - font-size: 0.9em; -} - -/* Code blocks */ -pre[class*='language-'] { - padding: 1em; - margin: 0.5em 0; - overflow: auto; -} - -:not(pre) > code[class*='language-'], -pre[class*='language-'] { - /* gray-900 */ - background: #18181b; -} - -/* Inline code */ -:not(pre) > code[class*='language-'] { - padding: 0.1em; - border-radius: 0.3em; - white-space: normal; -} - -.token.comment, -.token.block-comment, -.token.prolog, -.token.doctype, -.token.cdata { - /* gray-400 */ - color: #a1a1aa; -} - -.token.punctuation { - /* gray-200 */ - color: #e4e4e7; -} - -.token.tag, -.token.attr-name, -.token.namespace, -.token.deleted { - /* red-400 */ - color: #f87171; -} - -.token.function-name { - /* blue-200 */ - color: #bfdbfe; -} - -.token.boolean, -.token.number, -.token.function { - /* purple-200 */ - color: #c7d2fe; -} - -.token.property, -.token.class-name, -.token.constant, -.token.symbol { - /* purple-200 */ - color: #e9d5ff; -} - -.token.selector, -.token.important, -.token.atrule, -.token.keyword, -.token.builtin { - /* pink-200 */ - color: #fbcfe8; -} - -.token.string, -.token.char, -.token.attr-value, -.token.regex, -.token.variable { - /* green-200 */ - color: #bbf7d0; -} - -.token.operator, -.token.entity, -.token.url { - /* purple-400 */ - color: #c084fc; -} - -.token.important, -.token.bold { - font-weight: bold; -} - -.token.italic { - font-style: italic; -} - -.token.entity { - cursor: help; -} - -.token.inserted { - color: green; -} diff --git a/src/lib/vendor/prism/prism.js b/src/lib/vendor/prism/prism.js deleted file mode 100644 index e5ff04c0f..000000000 --- a/src/lib/vendor/prism/prism.js +++ /dev/null @@ -1,443 +0,0 @@ -// @ts-nocheck - -/* PrismJS 1.25.0 -https://prismjs.com/download.html#themes=prism-tomorrow&languages=json */ -var _self = - 'undefined' != typeof window - ? window - : 'undefined' != typeof WorkerGlobalScope && - self instanceof WorkerGlobalScope - ? self - : {}, - Prism = (function (u) { - var c = /\blang(?:uage)?-([\w-]+)\b/i, - n = 0, - e = {}, - M = { - manual: u.Prism && u.Prism.manual, - disableWorkerMessageHandler: - u.Prism && u.Prism.disableWorkerMessageHandler, - util: { - encode: function e(n) { - return n instanceof W - ? new W(n.type, e(n.content), n.alias) - : Array.isArray(n) - ? n.map(e) - : n - .replace(/&/g, '&') - .replace(/= l.reach); - y += m.value.length, m = m.next - ) { - var b = m.value; - if (t.length > n.length) return; - if (!(b instanceof W)) { - var k, - x = 1; - if (h) { - if (!(k = z(v, y, n, f))) break; - var w = k.index, - A = k.index + k[0].length, - P = y; - for (P += m.value.length; P <= w; ) - (m = m.next), (P += m.value.length); - if ( - ((P -= m.value.length), - (y = P), - m.value instanceof W) - ) - continue; - for ( - var E = m; - E !== t.tail && - (P < A || 'string' == typeof E.value); - E = E.next - ) - x++, (P += E.value.length); - x--, (b = n.slice(y, P)), (k.index -= y); - } else if (!(k = z(v, 0, b, f))) continue; - var w = k.index, - S = k[0], - O = b.slice(0, w), - L = b.slice(w + S.length), - N = y + b.length; - l && N > l.reach && (l.reach = N); - var j = m.prev; - O && ((j = I(t, j, O)), (y += O.length)), q(t, j, x); - var C = new W(o, g ? M.tokenize(S, g) : S, d, S); - if (((m = I(t, j, C)), L && I(t, m, L), 1 < x)) { - var _ = { cause: o + ',' + u, reach: N }; - e(n, t, r, m.prev, y, _), - l && _.reach > l.reach && (l.reach = _.reach); - } - } - } - } - } - })(e, a, n, a.head, 0), - (function (e) { - var n = [], - t = e.head.next; - for (; t !== e.tail; ) n.push(t.value), (t = t.next); - return n; - })(a) - ); - }, - hooks: { - all: {}, - add: function (e, n) { - var t = M.hooks.all; - (t[e] = t[e] || []), t[e].push(n); - }, - run: function (e, n) { - var t = M.hooks.all[e]; - if (t && t.length) for (var r, a = 0; (r = t[a++]); ) r(n); - }, - }, - Token: W, - }; - function W(e, n, t, r) { - (this.type = e), - (this.content = n), - (this.alias = t), - (this.length = 0 | (r || '').length); - } - function z(e, n, t, r) { - e.lastIndex = n; - var a = e.exec(t); - if (a && r && a[1]) { - var i = a[1].length; - (a.index += i), (a[0] = a[0].slice(i)); - } - return a; - } - function i() { - var e = { value: null, prev: null, next: null }, - n = { value: null, prev: e, next: null }; - (e.next = n), (this.head = e), (this.tail = n), (this.length = 0); - } - function I(e, n, t) { - var r = n.next, - a = { value: t, prev: n, next: r }; - return (n.next = a), (r.prev = a), e.length++, a; - } - function q(e, n, t) { - for (var r = n.next, a = 0; a < t && r !== e.tail; a++) r = r.next; - ((n.next = r).prev = n), (e.length -= a); - } - if ( - ((u.Prism = M), - (W.stringify = function n(e, t) { - if ('string' == typeof e) return e; - if (Array.isArray(e)) { - var r = ''; - return ( - e.forEach(function (e) { - r += n(e, t); - }), - r - ); - } - var a = { - type: e.type, - content: n(e.content, t), - tag: 'span', - classes: ['token', e.type], - attributes: {}, - language: t, - }, - i = e.alias; - i && - (Array.isArray(i) - ? Array.prototype.push.apply(a.classes, i) - : a.classes.push(i)), - M.hooks.run('wrap', a); - var l = ''; - for (var o in a.attributes) - l += - ' ' + - o + - '="' + - (a.attributes[o] || '').replace(/"/g, '"') + - '"'; - return ( - '<' + - a.tag + - ' class="' + - a.classes.join(' ') + - '"' + - l + - '>' + - a.content + - '' - ); - }), - !u.document) - ) - return ( - u.addEventListener && - (M.disableWorkerMessageHandler || - u.addEventListener( - 'message', - function (e) { - var n = JSON.parse(e.data), - t = n.language, - r = n.code, - a = n.immediateClose; - u.postMessage(M.highlight(r, M.languages[t], t)), - a && u.close(); - }, - !1, - )), - M - ); - var t = M.util.currentScript(); - function r() { - M.manual || M.highlightAll(); - } - if ( - (t && - ((M.filename = t.src), - t.hasAttribute('data-manual') && (M.manual = !0)), - !M.manual) - ) { - var a = document.readyState; - 'loading' === a || ('interactive' === a && t && t.defer) - ? document.addEventListener('DOMContentLoaded', r) - : window.requestAnimationFrame - ? window.requestAnimationFrame(r) - : window.setTimeout(r, 16); - } - return M; - })(_self); -'undefined' != typeof module && module.exports && (module.exports = Prism), - 'undefined' != typeof global && (global.Prism = Prism); -(Prism.languages.json = { - property: { - pattern: /(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/, - lookbehind: !0, - greedy: !0, - }, - string: { - pattern: /(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/, - lookbehind: !0, - greedy: !0, - }, - comment: { pattern: /\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/, greedy: !0 }, - number: /-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i, - punctuation: /[{}[\],]/, - operator: /:/, - boolean: /\b(?:true|false)\b/, - null: { pattern: /\bnull\b/, alias: 'keyword' }, -}), - (Prism.languages.webmanifest = Prism.languages.json); diff --git a/src/lib/vendor/sdk-logos/dot-net-logo.png b/src/lib/vendor/sdk-logos/dot-net-logo.png new file mode 100644 index 000000000..9594c5429 Binary files /dev/null and b/src/lib/vendor/sdk-logos/dot-net-logo.png differ diff --git a/src/lib/vendor/sdk-logos/go-logo.png b/src/lib/vendor/sdk-logos/go-logo.png new file mode 100644 index 000000000..70f0a1954 Binary files /dev/null and b/src/lib/vendor/sdk-logos/go-logo.png differ diff --git a/src/lib/vendor/sdk-logos/java-logo.png b/src/lib/vendor/sdk-logos/java-logo.png new file mode 100644 index 000000000..e88a81308 Binary files /dev/null and b/src/lib/vendor/sdk-logos/java-logo.png differ diff --git a/src/lib/vendor/sdk-logos/php-logo.png b/src/lib/vendor/sdk-logos/php-logo.png new file mode 100644 index 000000000..baaf70634 Binary files /dev/null and b/src/lib/vendor/sdk-logos/php-logo.png differ diff --git a/src/lib/vendor/sdk-logos/python-logo.png b/src/lib/vendor/sdk-logos/python-logo.png new file mode 100644 index 000000000..2c1ef19d5 Binary files /dev/null and b/src/lib/vendor/sdk-logos/python-logo.png differ diff --git a/src/lib/vendor/sdk-logos/ruby-logo.png b/src/lib/vendor/sdk-logos/ruby-logo.png new file mode 100644 index 000000000..7d7f641c0 Binary files /dev/null and b/src/lib/vendor/sdk-logos/ruby-logo.png differ diff --git a/src/lib/vendor/sdk-logos/rust-logo.png b/src/lib/vendor/sdk-logos/rust-logo.png new file mode 100644 index 000000000..086168c88 Binary files /dev/null and b/src/lib/vendor/sdk-logos/rust-logo.png differ diff --git a/src/lib/vendor/sdk-logos/ts-logo.png b/src/lib/vendor/sdk-logos/ts-logo.png new file mode 100644 index 000000000..cbc287583 Binary files /dev/null and b/src/lib/vendor/sdk-logos/ts-logo.png differ diff --git a/src/markdown.reset.css b/src/markdown.reset.css new file mode 100644 index 000000000..d6a6f0bbd --- /dev/null +++ b/src/markdown.reset.css @@ -0,0 +1,132 @@ +*, +body { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + vertical-align: baseline; +} + +body { + overscroll-behavior: none; + position: relative; + padding: 1rem; + white-space: pre-line; + font-family: sans-serif; +} + +h1 { + font-size: 2em; +} + +h2 { + font-size: 1.5em; +} + +h3 { + font-size: 1.17em; +} + +h4 { + font-size: 1em; +} + +h5 { + font-size: 0.83em; +} + +h6 { + font-size: 0.67em; +} + +blockquote, +q { + quotes: none; +} +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ''; + content: none; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +ul, +ol { + white-space: normal; +} + +li { + list-style-position: inside; +} + +li * { + display: inline; +} + +a { + gap: 0.5rem; + align-items: center; + border-radius: 0.25rem; + max-width: fit-content; + text-decoration: underline; + text-underline-offset: 2px; + cursor: pointer; +} + +blockquote { + padding-top: 0; + padding-bottom: 0; + padding-left: 0.5rem; + border-left: 4px solid; + border-left-color: #92a4c3; + background: #e8efff; + color: #121416; + p { + font-size: 1.25rem; + line-height: 1.75rem; + } +} + +code { + font-family: monospace; + padding-top: 0.125rem; + padding-bottom: 0.125rem; + padding-left: 0.25rem; + padding-right: 0.25rem; + border-radius: 0.25rem; + background: #e8efff; + color: #121416; +} + +pre { + font-family: monospace; + padding: 0.25rem; + border-radius: 0.25rem; + background: #e8efff; + color: #121416; + code { + padding: 0; + } +} + +body[data-theme='light'] { + background-color: #fff; + color: #121416; + a { + color: #444ce7; + } +} + +body[data-theme='dark'] { + background-color: #141414; + color: #f8fafc; + a { + color: #8098f9; + } +} diff --git a/src/routes/(app)/+layout.svelte b/src/routes/(app)/+layout.svelte new file mode 100644 index 000000000..862c6b2dc --- /dev/null +++ b/src/routes/(app)/+layout.svelte @@ -0,0 +1,269 @@ + + + + + +
+ + + + +
diff --git a/src/routes/(app)/+layout.ts b/src/routes/(app)/+layout.ts new file mode 100644 index 000000000..8552c57a0 --- /dev/null +++ b/src/routes/(app)/+layout.ts @@ -0,0 +1,56 @@ +import { redirect } from '@sveltejs/kit'; + +import type { LayoutData, LayoutLoad } from './$types'; + +import { fetchCluster, fetchSystemInfo } from '$lib/services/cluster-service'; +import { fetchNamespaces } from '$lib/services/namespaces-service'; +import { fetchSettings } from '$lib/services/settings-service'; +import { clearAuthUser, getAuthUser, setAuthUser } from '$lib/stores/auth-user'; +import type { GetClusterInfoResponse, GetSystemInfoResponse } from '$lib/types'; +import type { Settings } from '$lib/types/global'; +import { + cleanAuthUserCookie, + getAuthUserCookie, +} from '$lib/utilities/auth-user-cookie'; +import { isAuthorized } from '$lib/utilities/is-authorized'; +import { routeForLoginPage } from '$lib/utilities/route-for'; + +import '../../app.css'; + +export const load: LayoutLoad = async function ({ + fetch, +}): Promise { + const settings: Settings = await fetchSettings(fetch); + + if (!settings.auth.enabled) { + cleanAuthUserCookie(); + clearAuthUser(); + } + + const authUser = getAuthUserCookie(); + if (authUser?.accessToken) { + setAuthUser(authUser); + cleanAuthUserCookie(); + } + + const user = getAuthUser(); + + if (!isAuthorized(settings, user)) { + redirect(302, routeForLoginPage()); + } + + fetchNamespaces(settings, fetch); + + const cluster: GetClusterInfoResponse = await fetchCluster(settings, fetch); + const systemInfo: GetSystemInfoResponse = await fetchSystemInfo( + settings, + fetch, + ); + + return { + user, + settings, + cluster, + systemInfo, + }; +}; diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte new file mode 100644 index 000000000..c5bbac61b --- /dev/null +++ b/src/routes/(app)/+page.svelte @@ -0,0 +1,40 @@ + + + diff --git a/src/routes/(app)/+page.ts b/src/routes/(app)/+page.ts new file mode 100644 index 000000000..910a1649b --- /dev/null +++ b/src/routes/(app)/+page.ts @@ -0,0 +1,23 @@ +import { redirect } from '@sveltejs/kit'; + +import type { PageLoad } from './$types'; + +import { routeForWorkflows } from '$lib/utilities/route-for'; + +export const load: PageLoad = async ({ parent }) => { + const data = await parent(); + + const defaultNamespace = data?.settings?.defaultNamespace; + const isCloud = data.settings.runtimeEnvironment?.isCloud; + + if (isCloud) { + redirect( + 302, + routeForWorkflows({ + namespace: defaultNamespace, + }), + ); + } + + return { defaultNamespace }; +}; diff --git a/src/routes/(app)/import/+layout.svelte b/src/routes/(app)/import/+layout.svelte new file mode 100644 index 000000000..513a38464 --- /dev/null +++ b/src/routes/(app)/import/+layout.svelte @@ -0,0 +1,8 @@ + + + + diff --git a/src/routes/(app)/import/+page.ts b/src/routes/(app)/import/+page.ts new file mode 100644 index 000000000..01d356033 --- /dev/null +++ b/src/routes/(app)/import/+page.ts @@ -0,0 +1,11 @@ +import { redirect } from '@sveltejs/kit'; + +import type { PageLoad } from './$types'; + +import { routeForEventHistoryImport } from '$lib/utilities/route-for'; + +export const load: PageLoad = async () => { + const redirectPath = routeForEventHistoryImport(); + + redirect(302, redirectPath); +}; diff --git a/src/routes/(app)/import/events/+page.svelte b/src/routes/(app)/import/events/+page.svelte new file mode 100644 index 000000000..b6c336657 --- /dev/null +++ b/src/routes/(app)/import/events/+page.svelte @@ -0,0 +1,5 @@ + + + diff --git a/src/routes/(app)/import/events/[namespace]/[workflow]/[run]/history/+layout.svelte b/src/routes/(app)/import/events/[namespace]/[workflow]/[run]/history/+layout.svelte new file mode 100644 index 000000000..0fba0f3c7 --- /dev/null +++ b/src/routes/(app)/import/events/[namespace]/[workflow]/[run]/history/+layout.svelte @@ -0,0 +1,7 @@ + + + + + diff --git a/src/routes/(app)/import/events/[namespace]/[workflow]/[run]/history/+page.ts b/src/routes/(app)/import/events/[namespace]/[workflow]/[run]/history/+page.ts new file mode 100644 index 000000000..5a442c1ae --- /dev/null +++ b/src/routes/(app)/import/events/[namespace]/[workflow]/[run]/history/+page.ts @@ -0,0 +1,10 @@ +import { redirect } from '@sveltejs/kit'; + +import type { PageLoad } from './$types'; + +import { routeForEventHistoryImport } from '$lib/utilities/route-for'; + +export const load: PageLoad = async function () { + const redirectPath = routeForEventHistoryImport('feed'); + redirect(302, redirectPath); +}; diff --git a/src/routes/(app)/import/events/[namespace]/[workflow]/[run]/history/compact/+page.svelte b/src/routes/(app)/import/events/[namespace]/[workflow]/[run]/history/compact/+page.svelte new file mode 100644 index 000000000..00ea80039 --- /dev/null +++ b/src/routes/(app)/import/events/[namespace]/[workflow]/[run]/history/compact/+page.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/routes/(app)/import/events/[namespace]/[workflow]/[run]/history/feed/+page.svelte b/src/routes/(app)/import/events/[namespace]/[workflow]/[run]/history/feed/+page.svelte new file mode 100644 index 000000000..84bb97687 --- /dev/null +++ b/src/routes/(app)/import/events/[namespace]/[workflow]/[run]/history/feed/+page.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/routes/(app)/import/events/[namespace]/[workflow]/[run]/history/json/+page.svelte b/src/routes/(app)/import/events/[namespace]/[workflow]/[run]/history/json/+page.svelte new file mode 100644 index 000000000..987a83f32 --- /dev/null +++ b/src/routes/(app)/import/events/[namespace]/[workflow]/[run]/history/json/+page.svelte @@ -0,0 +1,6 @@ + + + diff --git a/src/routes/(app)/namespaces/+page.svelte b/src/routes/(app)/namespaces/+page.svelte new file mode 100644 index 000000000..8e10eda18 --- /dev/null +++ b/src/routes/(app)/namespaces/+page.svelte @@ -0,0 +1,54 @@ + + + +

+ {translate('common.namespaces')} +

+{#if $namespaces?.length > 0} + + + + + + + {#each visibleItems as namespace} + + + + {/each} +
{translate('common.namespaces')}
{translate('common.name')} + {namespace.namespaceInfo.name} +
+
+{:else} + +{/if} diff --git a/src/routes/(app)/namespaces/[namespace]/+layout.ts b/src/routes/(app)/namespaces/[namespace]/+layout.ts new file mode 100644 index 000000000..719c6a068 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/+layout.ts @@ -0,0 +1,18 @@ +import type { LayoutData, LayoutLoad } from './$types'; + +import { fetchSearchAttributesForNamespace } from '$lib/services/search-attributes-service'; +import { allSearchAttributes } from '$lib/stores/search-attributes'; + +export const load: LayoutLoad = async ({ + params, + parent, + fetch, +}): Promise => { + await parent(); + const attributes = await fetchSearchAttributesForNamespace( + params.namespace, + fetch, + ); + + allSearchAttributes.set(attributes); +}; diff --git a/src/routes/(app)/namespaces/[namespace]/+page.svelte b/src/routes/(app)/namespaces/[namespace]/+page.svelte new file mode 100644 index 000000000..c66e0e12b --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/+page.svelte @@ -0,0 +1,258 @@ + + + +

+ {translate('namespaces.namespace')}: {namespace?.namespaceInfo?.name} +

+

+ {namespace?.namespaceInfo?.description || ''} +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{`${translate('namespaces.namespace')} ${translate( + 'common.details', + )}`}
+

{translate('common.details')}

+
{translate('namespaces.owner')}{namespace?.namespaceInfo?.ownerEmail || + translate('common.unknown')}
{translate('namespaces.global')} + + {namespace?.isGlobalNamespace + ? translate('common.yes') + : translate('common.no')} + +
{translate('namespaces.retention-period')}{fromSecondsToDaysOrHours( + namespace?.config?.workflowExecutionRetentionTtl.toString(), + )}
{translate('namespaces.history-archival')} + {namespace?.config?.historyArchivalState} +
{translate('namespaces.visibility-archival')} + {namespace?.config?.visibilityArchivalState} +
{translate('namespaces.failover-version')}{namespace?.failoverVersion || ''}
{translate('namespaces.clusters')}{clusters}
+
+ +
+ + + + + + + + + + + + + + +
{translate('namespaces.versions')}
+

{translate('namespaces.versions')}

+
Temporal Server Version{$temporalVersion}
Temporal UI Version{$uiVersion}
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
{translate('namespaces.client-actions')}
+

+ {translate('namespaces.client-actions')} +

+
{translate('namespaces.client-actions')}{badgeTextForBoolean($settings.disableWriteActions)}
{translate('workflows.terminate-modal-title')}{badgeTextForBoolean($settings.workflowTerminateDisabled)}
{translate('workflows.cancel-modal-title')}{badgeTextForBoolean($settings.workflowCancelDisabled)}
{translate('namespaces.signal-workflow')}{badgeTextForBoolean($settings.workflowSignalDisabled)}
{translate('workflows.reset-modal-title')}{badgeTextForBoolean($settings.workflowResetDisabled)}
+
+
+ +{#if $searchAttributes} +
+

+ {translate('events.attribute-group.search-attributes')} +

+ + + + + + + {#each Object.entries($searchAttributes) as [key, type]} + + + + + {/each} +
{translate('events.attribute-group.search-attributes')}
{translate('common.key')}{translate('common.type')}{key}{type}
+
+{/if} diff --git a/src/routes/(app)/namespaces/[namespace]/+page.ts b/src/routes/(app)/namespaces/[namespace]/+page.ts new file mode 100644 index 000000000..178535acc --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/+page.ts @@ -0,0 +1,19 @@ +import type { PageLoad } from './$types'; + +import { fetchNamespace } from '$lib/services/namespaces-service'; +import { getClusters } from '$lib/utilities/get-clusters'; + +export const load: PageLoad = async function ({ params, parent, url }) { + const { searchParams } = url; + + if (searchParams.has('time-range')) searchParams.delete('time-range'); + + await parent(); + const namespace = await fetchNamespace(params.namespace); + const clusters = getClusters(namespace); + + return { + namespace, + clusters, + }; +}; diff --git a/src/routes/(app)/namespaces/[namespace]/archival/+page.svelte b/src/routes/(app)/namespaces/[namespace]/archival/+page.svelte new file mode 100644 index 000000000..503fc7b17 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/archival/+page.svelte @@ -0,0 +1,107 @@ + + + +{#if archivalEnabled && visibilityArchivalEnabled} +

+ {translate('workflows.archived-workflows')} +

+ {#if !archivalQueryingNotSupported}{/if} + {#if workflows?.length} + + + {#each visibleItems as event} + + {/each} + + + {:else} + + {/if} +{:else if archivalEnabled} +

+ {translate('workflows.visibility-disabled-archival')} +

+

+ {translate('workflows.archival-link-preface')}{translate('workflows.archival-link')}: +

+ +{:else} +

+ {translate('workflows.archival-disabled-title')} +

+

{translate('workflows.archival-disabled-details')}:

+ + {#if !visibilityArchivalEnabled} +

+ {translate('workflows.archival-link-preface')}{translate('workflows.archival-link')}: +

+ + {/if} +{/if} diff --git a/src/routes/(app)/namespaces/[namespace]/archival/+page.ts b/src/routes/(app)/namespaces/[namespace]/archival/+page.ts new file mode 100644 index 000000000..b75904f75 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/archival/+page.ts @@ -0,0 +1,65 @@ +import type { PageLoad } from './$types'; + +import { fetchNamespace } from '$lib/services/namespaces-service'; +import { + type CombinedWorkflowExecutionsResponse, + fetchAllArchivedWorkflows, +} from '$lib/services/workflow-service'; +import type { DescribeNamespaceResponse } from '$lib/types'; +import type { + ArchiveFilterParameters, + WorkflowStatus, +} from '$lib/types/workflows'; + +export const load: PageLoad = async function ({ params, url }) { + const { searchParams } = url; + + if (!searchParams.has('time-range')) searchParams.set('time-range', '1 day'); + + const workflowId = searchParams.get('workflow-id'); + const workflowType = searchParams.get('workflow-type'); + const timeRange = searchParams.get('time-range'); + const executionStatus = searchParams.get('status') as WorkflowStatus; + + const namespace: DescribeNamespaceResponse = await fetchNamespace( + params.namespace, + ); + const isS3Bucket = namespace.config?.historyArchivalUri + ?.toLowerCase() + ?.startsWith('s3://'); + const isGSBucket = namespace.config?.historyArchivalUri + ?.toLowerCase() + ?.startsWith('gs://'); + const archivalQueryingNotSupported = isS3Bucket || isGSBucket; + + const parameters: ArchiveFilterParameters = archivalQueryingNotSupported + ? {} + : { + workflowId, + workflowType, + closeTime: timeRange, + executionStatus, + }; + + // These are incorrectly typed as enums and need to be coerced to strings + const archivalEnabled = + (namespace?.config?.historyArchivalState as unknown as string) === + 'Enabled'; + + const visibilityArchivalEnabled = + (namespace?.config?.visibilityArchivalState as unknown as string) === + 'Enabled'; + + const initialData: CombinedWorkflowExecutionsResponse | null = + archivalEnabled && visibilityArchivalEnabled + ? await fetchAllArchivedWorkflows(params.namespace, parameters, fetch) + : null; + + return { + workflows: initialData ? initialData.workflows : [], + namespace, + archivalEnabled, + visibilityArchivalEnabled, + archivalQueryingNotSupported, + }; +}; diff --git a/src/routes/(app)/namespaces/[namespace]/archival/_filter-input.svelte b/src/routes/(app)/namespaces/[namespace]/archival/_filter-input.svelte new file mode 100644 index 000000000..66b0e89bf --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/archival/_filter-input.svelte @@ -0,0 +1,35 @@ + + + diff --git a/src/routes/(app)/namespaces/[namespace]/archival/_workflow-filters.svelte b/src/routes/(app)/namespaces/[namespace]/archival/_workflow-filters.svelte new file mode 100644 index 000000000..bc1b34137 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/archival/_workflow-filters.svelte @@ -0,0 +1,117 @@ + + +
+

+ {#if isAdvancedQuery} + + {translate('workflows.basic-search')} + + {:else} + + {translate('workflows.advanced-search')} + + {/if} +

+ + {#if isAdvancedQuery} + + {:else} + + {/if} +
diff --git a/src/routes/(app)/namespaces/[namespace]/batch-operations/+page.svelte b/src/routes/(app)/namespaces/[namespace]/batch-operations/+page.svelte new file mode 100644 index 000000000..439abd178 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/batch-operations/+page.svelte @@ -0,0 +1,12 @@ + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/batch-operations/[jobId]/+page.svelte b/src/routes/(app)/namespaces/[namespace]/batch-operations/[jobId]/+page.svelte new file mode 100644 index 000000000..24c86856c --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/batch-operations/[jobId]/+page.svelte @@ -0,0 +1,13 @@ + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/schedules/+page.svelte b/src/routes/(app)/namespaces/[namespace]/schedules/+page.svelte new file mode 100644 index 000000000..8e0713032 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/schedules/+page.svelte @@ -0,0 +1,12 @@ + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/schedules/[schedule]/+page.svelte b/src/routes/(app)/namespaces/[namespace]/schedules/[schedule]/+page.svelte new file mode 100644 index 000000000..ea07fe8c9 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/schedules/[schedule]/+page.svelte @@ -0,0 +1,11 @@ + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/schedules/[schedule]/edit/+page.svelte b/src/routes/(app)/namespaces/[namespace]/schedules/[schedule]/edit/+page.svelte new file mode 100644 index 000000000..d3df3147f --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/schedules/[schedule]/edit/+page.svelte @@ -0,0 +1,12 @@ + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/schedules/create/+page.svelte b/src/routes/(app)/namespaces/[namespace]/schedules/create/+page.svelte new file mode 100644 index 000000000..4667184f5 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/schedules/create/+page.svelte @@ -0,0 +1,11 @@ + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/search-attributes/+page.svelte b/src/routes/(app)/namespaces/[namespace]/search-attributes/+page.svelte new file mode 100644 index 000000000..022854103 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/search-attributes/+page.svelte @@ -0,0 +1,19 @@ + + + + Custom Search Attributes + + +
+
+

Custom Search Attributes

+

+ Manage custom search attributes for this namespace. +

+
+ + +
diff --git a/src/routes/(app)/namespaces/[namespace]/task-queues/+layout.svelte b/src/routes/(app)/namespaces/[namespace]/task-queues/+layout.svelte new file mode 100644 index 000000000..3c757a7b6 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/task-queues/+layout.svelte @@ -0,0 +1,8 @@ + + +

+ {translate('common.task-queue')} +

+ diff --git a/src/routes/namespaces/[namespace]/task-queues/index.svelte b/src/routes/(app)/namespaces/[namespace]/task-queues/+page.svelte similarity index 100% rename from src/routes/namespaces/[namespace]/task-queues/index.svelte rename to src/routes/(app)/namespaces/[namespace]/task-queues/+page.svelte diff --git a/src/routes/(app)/namespaces/[namespace]/task-queues/[queue]/+page.svelte b/src/routes/(app)/namespaces/[namespace]/task-queues/[queue]/+page.svelte new file mode 100644 index 000000000..fb2e8c670 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/task-queues/[queue]/+page.svelte @@ -0,0 +1,11 @@ + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.svelte b/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.svelte new file mode 100644 index 000000000..da3d7ee38 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/worker-deployments/+page.svelte @@ -0,0 +1,9 @@ + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/worker-deployments/[deployment]/+layout.svelte b/src/routes/(app)/namespaces/[namespace]/worker-deployments/[deployment]/+layout.svelte new file mode 100644 index 000000000..cab3bdcc4 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/worker-deployments/[deployment]/+layout.svelte @@ -0,0 +1,12 @@ + + +
+ +
diff --git a/src/routes/(app)/namespaces/[namespace]/worker-deployments/[deployment]/+page.svelte b/src/routes/(app)/namespaces/[namespace]/worker-deployments/[deployment]/+page.svelte new file mode 100644 index 000000000..afc428d7e --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/worker-deployments/[deployment]/+page.svelte @@ -0,0 +1,11 @@ + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/+page.svelte new file mode 100644 index 000000000..f5a20dcbb --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/+page.svelte @@ -0,0 +1,13 @@ + + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/+page.ts b/src/routes/(app)/namespaces/[namespace]/workflows/+page.ts new file mode 100644 index 000000000..a3d15781a --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/+page.ts @@ -0,0 +1 @@ +export const ssr = false; diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/+page.ts b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/+page.ts new file mode 100644 index 000000000..fa6cf1448 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/+page.ts @@ -0,0 +1,16 @@ +import { error, redirect } from '@sveltejs/kit'; + +import type { PageLoad } from './$types'; + +import { fetchWorkflowForRunId } from '$lib/services/workflow-service'; + +export const load: PageLoad = async function ({ url, params }) { + const { namespace, workflow: workflowId } = params; + const { runId } = await fetchWorkflowForRunId({ namespace, workflowId }); + + if (runId) { + redirect(302, `${url.pathname}/${runId}`); + } else { + error(404); + } +}; diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/+layout.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/+layout.svelte new file mode 100644 index 000000000..0885e370e --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/+layout.svelte @@ -0,0 +1,14 @@ + + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/+page.svelte new file mode 100644 index 000000000..e69de29bb diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/+page.ts b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/+page.ts new file mode 100644 index 000000000..77fb19800 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/+page.ts @@ -0,0 +1,7 @@ +import { redirect } from '@sveltejs/kit'; + +import type { PageLoad } from './$types'; + +export const load: PageLoad = async function ({ url }) { + redirect(302, `${url.pathname}/history`); +}; diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/call-stack/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/call-stack/+page.svelte new file mode 100644 index 000000000..7df0078fc --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/call-stack/+page.svelte @@ -0,0 +1,18 @@ + + + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/history.json/+page.ts b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/history.json/+page.ts new file mode 100644 index 000000000..2f6b3218d --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/history.json/+page.ts @@ -0,0 +1,17 @@ +import { redirect } from '@sveltejs/kit'; + +import type { PageLoad } from './$types'; + +import { getEndpointForRawHistory } from '$lib/services/events-service'; + +export const load: PageLoad = async function ({ parent, params }) { + await parent(); + + const { namespace, workflow, run } = params; + const route = getEndpointForRawHistory({ + namespace, + workflowId: workflow, + runId: run, + }); + redirect(302, route); +}; diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/history/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/history/+page.svelte new file mode 100644 index 000000000..6e4c8ed77 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/history/+page.svelte @@ -0,0 +1,14 @@ + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/history/compact/+page.ts b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/history/compact/+page.ts new file mode 100644 index 000000000..6dfdd6f39 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/history/compact/+page.ts @@ -0,0 +1,15 @@ +import { redirect } from '@sveltejs/kit'; + +import type { PageLoad } from './$types'; + +import { routeForEventHistory } from '$lib/utilities/route-for'; + +export const load: PageLoad = async function ({ params }) { + const { namespace, workflow, run } = params; + const route = routeForEventHistory({ + namespace, + workflow, + run, + }); + redirect(302, route); +}; diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/history/events/[id]/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/history/events/[id]/+page.svelte new file mode 100644 index 000000000..22310cb62 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/history/events/[id]/+page.svelte @@ -0,0 +1,18 @@ + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/history/feed/+page.ts b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/history/feed/+page.ts new file mode 100644 index 000000000..6dfdd6f39 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/history/feed/+page.ts @@ -0,0 +1,15 @@ +import { redirect } from '@sveltejs/kit'; + +import type { PageLoad } from './$types'; + +import { routeForEventHistory } from '$lib/utilities/route-for'; + +export const load: PageLoad = async function ({ params }) { + const { namespace, workflow, run } = params; + const route = routeForEventHistory({ + namespace, + workflow, + run, + }); + redirect(302, route); +}; diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/history/json/+page.ts b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/history/json/+page.ts new file mode 100644 index 000000000..6dfdd6f39 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/history/json/+page.ts @@ -0,0 +1,15 @@ +import { redirect } from '@sveltejs/kit'; + +import type { PageLoad } from './$types'; + +import { routeForEventHistory } from '$lib/utilities/route-for'; + +export const load: PageLoad = async function ({ params }) { + const { namespace, workflow, run } = params; + const route = routeForEventHistory({ + namespace, + workflow, + run, + }); + redirect(302, route); +}; diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/metadata/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/metadata/+page.svelte new file mode 100644 index 000000000..244ca90eb --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/metadata/+page.svelte @@ -0,0 +1,18 @@ + + + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/pending-activities/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/pending-activities/+page.svelte new file mode 100644 index 000000000..dcf3f0a90 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/pending-activities/+page.svelte @@ -0,0 +1,18 @@ + + + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/query/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/query/+page.svelte new file mode 100644 index 000000000..1bec4adfd --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/query/+page.svelte @@ -0,0 +1,18 @@ + + + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/relationships/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/relationships/+page.svelte new file mode 100644 index 000000000..552406ac2 --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/relationships/+page.svelte @@ -0,0 +1,15 @@ + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/stack-trace/+page.ts b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/stack-trace/+page.ts new file mode 100644 index 000000000..9c4b276ac --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/stack-trace/+page.ts @@ -0,0 +1,9 @@ +import { redirect } from '@sveltejs/kit'; + +import type { PageLoad } from './$types'; + +import { routeForCallStack } from '$lib/utilities/route-for'; + +export const load: PageLoad = async function ({ params }) { + redirect(302, routeForCallStack(params)); +}; diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte new file mode 100644 index 000000000..0d772fc0a --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/[workflow]/[run]/workers/+page.svelte @@ -0,0 +1,20 @@ + + + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/start-workflow/+page.svelte b/src/routes/(app)/namespaces/[namespace]/workflows/start-workflow/+page.svelte new file mode 100644 index 000000000..c7e1e86be --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/start-workflow/+page.svelte @@ -0,0 +1,9 @@ + + + + diff --git a/src/routes/(app)/namespaces/[namespace]/workflows/start-workflow/+page.ts b/src/routes/(app)/namespaces/[namespace]/workflows/start-workflow/+page.ts new file mode 100644 index 000000000..91a81d71a --- /dev/null +++ b/src/routes/(app)/namespaces/[namespace]/workflows/start-workflow/+page.ts @@ -0,0 +1,17 @@ +import { redirect } from '@sveltejs/kit'; + +import type { PageLoad } from './$types'; + +import { routeForWorkflows } from '$lib/utilities/route-for'; + +export const load: PageLoad = async function ({ params, parent }) { + const data = await parent(); + const disabled = + data?.settings?.disableWriteActions || + data?.settings?.startWorkflowDisabled; + + if (disabled) { + const { namespace } = params; + redirect(302, routeForWorkflows({ namespace })); + } +}; diff --git a/src/routes/(app)/nexus/+layout.svelte b/src/routes/(app)/nexus/+layout.svelte new file mode 100644 index 000000000..db3004c1a --- /dev/null +++ b/src/routes/(app)/nexus/+layout.svelte @@ -0,0 +1,13 @@ + + + +
+

+ Sorry, this feature is not available in this version of Temporal. Please + enable Nexus or upgrade to a newer version. +

+
+ +
diff --git a/src/routes/(app)/nexus/+page.svelte b/src/routes/(app)/nexus/+page.svelte new file mode 100644 index 000000000..1b7c46c94 --- /dev/null +++ b/src/routes/(app)/nexus/+page.svelte @@ -0,0 +1,15 @@ + + + + diff --git a/src/routes/(app)/nexus/+page.ts b/src/routes/(app)/nexus/+page.ts new file mode 100644 index 000000000..4e525bde3 --- /dev/null +++ b/src/routes/(app)/nexus/+page.ts @@ -0,0 +1,11 @@ +import { fetchNexusEndpoints } from '$lib/services/nexus-service.js'; + +import type { PageLoad } from '../$types'; + +export const load: PageLoad = async ({ fetch, url }) => { + const search = url.searchParams.get('search') || ''; + const endpoints = await fetchNexusEndpoints(search, fetch); + return { + endpoints, + }; +}; diff --git a/src/routes/(app)/nexus/[id]/+layout.svelte b/src/routes/(app)/nexus/[id]/+layout.svelte new file mode 100644 index 000000000..08cf9e9d7 --- /dev/null +++ b/src/routes/(app)/nexus/[id]/+layout.svelte @@ -0,0 +1,22 @@ + + +{#if !endpoint} + +{:else} + +{/if} diff --git a/src/routes/(app)/nexus/[id]/+layout.ts b/src/routes/(app)/nexus/[id]/+layout.ts new file mode 100644 index 000000000..b33c2c060 --- /dev/null +++ b/src/routes/(app)/nexus/[id]/+layout.ts @@ -0,0 +1,33 @@ +import { fetchNexusEndpoint } from '$lib/services/nexus-service.js'; +import { decodeSingleReadablePayloadWithCodec } from '$lib/utilities/decode-payload.js'; + +export const load = async ({ params, fetch, parent }) => { + const { id } = params; + + try { + const { endpoint } = await fetchNexusEndpoint(id, fetch); + const data = await parent(); + + let description = ''; + try { + if (endpoint?.spec?.description?.data) { + const decodedDescription = await decodeSingleReadablePayloadWithCodec( + endpoint.spec.description, + data.settings, + ); + + if (typeof decodedDescription === 'string') { + description = decodedDescription; + } + } + } catch (e) { + console.error('Error decoding Nexus Endpoint description:', e); + } + endpoint.spec.descriptionString = description; + return { + endpoint, + }; + } catch (e) { + console.error('Error loading Nexus Endpoint:', e); + } +}; diff --git a/src/routes/(app)/nexus/[id]/+page.svelte b/src/routes/(app)/nexus/[id]/+page.svelte new file mode 100644 index 000000000..acc8460e2 --- /dev/null +++ b/src/routes/(app)/nexus/[id]/+page.svelte @@ -0,0 +1,19 @@ + + + + diff --git a/src/routes/(app)/nexus/[id]/edit/+page.svelte b/src/routes/(app)/nexus/[id]/edit/+page.svelte new file mode 100644 index 000000000..10ef2de24 --- /dev/null +++ b/src/routes/(app)/nexus/[id]/edit/+page.svelte @@ -0,0 +1,87 @@ + + + + diff --git a/src/routes/(app)/nexus/create/+page.svelte b/src/routes/(app)/nexus/create/+page.svelte new file mode 100644 index 000000000..d0c09b432 --- /dev/null +++ b/src/routes/(app)/nexus/create/+page.svelte @@ -0,0 +1,47 @@ + + + + diff --git a/src/routes/(app)/render/+server.ts b/src/routes/(app)/render/+server.ts new file mode 100644 index 000000000..27c1c2a25 --- /dev/null +++ b/src/routes/(app)/render/+server.ts @@ -0,0 +1,83 @@ +import fs from 'fs'; +import crypto from 'node:crypto'; +import path from 'path'; + +import { toHtml } from 'hast-util-to-html'; +import { h } from 'hastscript'; +import { toHast } from 'mdast-util-to-hast'; + +import { process } from '$lib/utilities/render-markdown'; + +type RenderOptions = { + host: string; + nonce: string; + theme?: string; +}; + +/** + * Generate a random nonce. + */ +const generateNonce = (): string => crypto.randomBytes(16).toString('hex'); + +/** + * Generate a Content Security Policy header value. + * @param nonce + * @returns + */ +const generateContentSecurityPolicy = ({ nonce }: RenderOptions) => { + return `base-uri 'self'; default-src 'none'; style-src 'nonce-${nonce}'; script-src 'nonce-${nonce}'; frame-ancestors 'self'; form-action 'none'; sandbox allow-same-origin allow-popups allow-popups-to-escape-sandbox;`; +}; + +/** + * Create a new HTML page with the given AST. + */ +const createPage = ( + ast: ReturnType, + { nonce, theme }: RenderOptions, +) => { + const cssPath = path.resolve('src/markdown.reset.css'); + const css = fs.readFileSync(cssPath, 'utf8'); + return toHtml( + h('html', [ + h('head', [ + h('title', 'Rendered Markdown'), + h('base', { target: '_blank' }), + h('meta', { charset: 'utf-8' }), + h('meta', { + name: 'viewport', + content: 'width=device-width, initial-scale=1', + }), + h('style', { nonce }, css), + ]), + h('body.prose', { 'data-theme': theme }, h('main', ast)), + ]), + ); +}; + +export const GET = async (req: Request) => { + const url = new URL(req.url); + + const host = url.origin; + const content = url.searchParams.get('content') || ''; + const theme = url.searchParams.get('theme') || ''; + + if (host === null) return new Response('Not found', { status: 404 }); + if (content === null) return new Response('Not found', { status: 404 }); + + const nonce = generateNonce(); + + const response = new Response( + createPage(await process(content), { nonce, host, theme }), + { + headers: { + 'Content-Type': 'text/html', + 'Content-Security-Policy': generateContentSecurityPolicy({ + nonce, + host, + }), + }, + }, + ); + + return response; +}; diff --git a/src/routes/(app)/select-namespace/+page.svelte b/src/routes/(app)/select-namespace/+page.svelte new file mode 100644 index 000000000..3442ba9de --- /dev/null +++ b/src/routes/(app)/select-namespace/+page.svelte @@ -0,0 +1,87 @@ + + + +
+

+ {translate('namespaces.select-namespace-welcome')} +

+

{translate('namespaces.select-namespace')}

+ +
+ {#if items.length} + + + + {:else} + + {/if} +
+
diff --git a/src/routes/namespaces/[namespace]/schedules/__layout@root.svelte b/src/routes/(login)/+layout.svelte similarity index 100% rename from src/routes/namespaces/[namespace]/schedules/__layout@root.svelte rename to src/routes/(login)/+layout.svelte diff --git a/src/routes/(login)/+layout.ts b/src/routes/(login)/+layout.ts new file mode 100644 index 000000000..6aafd3294 --- /dev/null +++ b/src/routes/(login)/+layout.ts @@ -0,0 +1,22 @@ +import '../../app.css'; + +import { error } from '@sveltejs/kit'; + +import type { LayoutLoad } from './$types'; + +import { fetchSettings } from '$lib/services/settings-service'; +import type { Settings } from '$lib/types/global'; + +export const ssr = false; + +export const load: LayoutLoad = async function ({ fetch }) { + const settings: Settings = await fetchSettings(fetch); + + if (!settings.auth.enabled) { + error(404); + } + + return { + settings, + }; +}; diff --git a/src/routes/(login)/login/+page.svelte b/src/routes/(login)/login/+page.svelte new file mode 100644 index 000000000..7d6d19b0d --- /dev/null +++ b/src/routes/(login)/login/+page.svelte @@ -0,0 +1,55 @@ + + + +
+ + +
+
+

+ Welcome back. +

+

Let's get you signed in.

+
+ +
+ + {#if error} +
+

+ {error} +

+
+ {/if} +
diff --git a/src/routes/(login)/signin/+page.svelte b/src/routes/(login)/signin/+page.svelte new file mode 100644 index 000000000..7d6d19b0d --- /dev/null +++ b/src/routes/(login)/signin/+page.svelte @@ -0,0 +1,55 @@ + + + +
+ + +
+
+

+ Welcome back. +

+

Let's get you signed in.

+
+ +
+ + {#if error} +
+

+ {error} +

+
+ {/if} +
diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte new file mode 100644 index 000000000..0c55d6e0f --- /dev/null +++ b/src/routes/+error.svelte @@ -0,0 +1,29 @@ + + + + + diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts new file mode 100644 index 000000000..0b3b8c258 --- /dev/null +++ b/src/routes/+layout.ts @@ -0,0 +1,25 @@ +import i18next from 'i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; + +import type { LayoutData, LayoutLoad } from './$types'; + +import { i18nNamespaces } from '$lib/i18n'; +import resources from '$lib/i18n/locales'; + +export const ssr = false; + +export const load: LayoutLoad = async function (): LayoutData { + i18next.use(LanguageDetector).init({ + fallbackLng: 'en', + load: 'languageOnly', + ns: i18nNamespaces, + defaultNS: 'common', + detection: { + order: ['querystring', 'localStorage', 'navigator'], + caches: ['localStorage'], + lookupQuerystring: 'lng', + lookupLocalStorage: 'locale', + }, + resources, + }); +}; diff --git a/src/routes/__error.svelte b/src/routes/__error.svelte deleted file mode 100644 index 98c037318..000000000 --- a/src/routes/__error.svelte +++ /dev/null @@ -1,35 +0,0 @@ - - - - - diff --git a/src/routes/__layout-root.svelte b/src/routes/__layout-root.svelte deleted file mode 100644 index e5eb6fb5d..000000000 --- a/src/routes/__layout-root.svelte +++ /dev/null @@ -1,98 +0,0 @@ - - - - -
- - -
-
-
-
- -
- {}}> - - -
- -
-
diff --git a/src/routes/_header.svelte b/src/routes/_header.svelte deleted file mode 100644 index f4686c358..000000000 --- a/src/routes/_header.svelte +++ /dev/null @@ -1,88 +0,0 @@ - - - Promise.resolve(namespaceList)} - {activeNamespace} - {linkList} - {isCloud} - user={Promise.resolve(user)} - {logout} - extras={[ - { - component: DataEncoderStatus, - name: 'Data Encoder', - onClick: () => ($showDataEncoderSettings = true), - }, - ]} -/> diff --git a/src/routes/data-converter/[port].svelte b/src/routes/data-converter/[port].svelte deleted file mode 100644 index 40824ebaf..000000000 --- a/src/routes/data-converter/[port].svelte +++ /dev/null @@ -1,13 +0,0 @@ - diff --git a/src/routes/import/__layout-import.svelte b/src/routes/import/__layout-import.svelte deleted file mode 100644 index bdf07d86d..000000000 --- a/src/routes/import/__layout-import.svelte +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - Temporal - - - - - -
- -
-
-
-
-
- {}}> - - -
-
-
diff --git a/src/routes/import/_event-history-import.svelte b/src/routes/import/_event-history-import.svelte deleted file mode 100644 index 4be3dcfc5..000000000 --- a/src/routes/import/_event-history-import.svelte +++ /dev/null @@ -1,58 +0,0 @@ - - - - diff --git a/src/routes/import/_import-header.svelte b/src/routes/import/_import-header.svelte deleted file mode 100644 index 4b8faf9ba..000000000 --- a/src/routes/import/_import-header.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/src/routes/import/_import-settings.ts b/src/routes/import/_import-settings.ts deleted file mode 100644 index 5f3ba282d..000000000 --- a/src/routes/import/_import-settings.ts +++ /dev/null @@ -1,19 +0,0 @@ -export const importSettings = { - auth: { - enabled: false, - options: [], - }, - baseUrl: 'base', - codec: {}, - defaultNamespace: 'namespace', - disableWriteActions: false, - showTemporalSystemNamespace: false, - notifyOnNewVersion: false, - feedbackURL: '', - runtimeEnvironment: { - isCloud: false, - isLocal: true, - envOverride: false, - }, - version: '2.0.0', -}; diff --git a/src/routes/import/events/[namespace]/[workflow]/[run]/history/__layout@import.svelte b/src/routes/import/events/[namespace]/[workflow]/[run]/history/__layout@import.svelte deleted file mode 100644 index 9da56b61d..000000000 --- a/src/routes/import/events/[namespace]/[workflow]/[run]/history/__layout@import.svelte +++ /dev/null @@ -1,39 +0,0 @@ - - -
- - - -
diff --git a/src/routes/import/events/[namespace]/[workflow]/[run]/history/compact/index.svelte b/src/routes/import/events/[namespace]/[workflow]/[run]/history/compact/index.svelte deleted file mode 100644 index 14a7bfee6..000000000 --- a/src/routes/import/events/[namespace]/[workflow]/[run]/history/compact/index.svelte +++ /dev/null @@ -1,18 +0,0 @@ - - - diff --git a/src/routes/import/events/[namespace]/[workflow]/[run]/history/feed/index.svelte b/src/routes/import/events/[namespace]/[workflow]/[run]/history/feed/index.svelte deleted file mode 100644 index 12894d465..000000000 --- a/src/routes/import/events/[namespace]/[workflow]/[run]/history/feed/index.svelte +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/src/routes/import/events/[namespace]/[workflow]/[run]/history/index.svelte b/src/routes/import/events/[namespace]/[workflow]/[run]/history/index.svelte deleted file mode 100644 index 59224e4b1..000000000 --- a/src/routes/import/events/[namespace]/[workflow]/[run]/history/index.svelte +++ /dev/null @@ -1,9 +0,0 @@ - diff --git a/src/routes/import/events/[namespace]/[workflow]/[run]/history/json/index.svelte b/src/routes/import/events/[namespace]/[workflow]/[run]/history/json/index.svelte deleted file mode 100644 index a9bd0b570..000000000 --- a/src/routes/import/events/[namespace]/[workflow]/[run]/history/json/index.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/src/routes/import/events/index@import.svelte b/src/routes/import/events/index@import.svelte deleted file mode 100644 index c91e0623f..000000000 --- a/src/routes/import/events/index@import.svelte +++ /dev/null @@ -1,38 +0,0 @@ - - -
-
- -
-
- type HistoryEvent = temporal.api.history.v1.IHistoryEvent -
- View in Github -
-

Expected JSON formats

-
- - -
-
-
diff --git a/src/routes/import/index.svelte b/src/routes/import/index.svelte deleted file mode 100644 index 4c1a2954f..000000000 --- a/src/routes/import/index.svelte +++ /dev/null @@ -1,14 +0,0 @@ - diff --git a/src/routes/index@root.svelte b/src/routes/index@root.svelte deleted file mode 100644 index c578500b3..000000000 --- a/src/routes/index@root.svelte +++ /dev/null @@ -1,59 +0,0 @@ - - - - - diff --git a/src/routes/login/__layout-login.svelte b/src/routes/login/__layout-login.svelte deleted file mode 100644 index c0d32801f..000000000 --- a/src/routes/login/__layout-login.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/src/routes/login/index@login.svelte b/src/routes/login/index@login.svelte deleted file mode 100644 index fd9f5b75e..000000000 --- a/src/routes/login/index@login.svelte +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - -
-

Welcome back.

-

Let's get you signed in.

-
- -
- - {#if error} -
-

- {error} -

-
- {/if} -
diff --git a/src/routes/namespaces/[namespace]/archival/_filter-input.svelte b/src/routes/namespaces/[namespace]/archival/_filter-input.svelte deleted file mode 100644 index ceabeb3bb..000000000 --- a/src/routes/namespaces/[namespace]/archival/_filter-input.svelte +++ /dev/null @@ -1,44 +0,0 @@ - - -
- - - -
- - diff --git a/src/routes/namespaces/[namespace]/archival/_workflow-filters.svelte b/src/routes/namespaces/[namespace]/archival/_workflow-filters.svelte deleted file mode 100644 index e345b46d0..000000000 --- a/src/routes/namespaces/[namespace]/archival/_workflow-filters.svelte +++ /dev/null @@ -1,106 +0,0 @@ - - -
-

- {#if isAdvancedQuery} - - Basic Search - - {:else} - - Advanced Search - - {/if} -

- - {#if isAdvancedQuery} - - {:else} -
- - - - {#each durations as value} - - {/each} - - - {#each Object.entries(statuses) as [label, value] (label)} - - {/each} - - -
- {/if} -
diff --git a/src/routes/namespaces/[namespace]/archival/index@root.svelte b/src/routes/namespaces/[namespace]/archival/index@root.svelte deleted file mode 100644 index 116977d1d..000000000 --- a/src/routes/namespaces/[namespace]/archival/index@root.svelte +++ /dev/null @@ -1,138 +0,0 @@ - - - - - -{#if archivalEnabled && visibilityArchivalEnabled} -

Archived Workflows

- - {#if workflows?.length} - - - {#each visibleItems as event} - - {/each} - - - {:else} - - {/if} -{:else if archivalEnabled} -

- This namespace is currently enabled for archival but visibility is not - enabled. -

-

To enable Visibility Archival:

- -

- For more details, please check out Archival documentation. -

-{:else} -

- This namespace is currently not enabled for archival. -

-

Run this command to enable Archival for Event Histories:

- - {#if !visibilityArchivalEnabled} -

To enable Visibility Archival:

- - {/if} -

- For more details, please check out Archival documentation. -

-{/if} diff --git a/src/routes/namespaces/[namespace]/index@root.svelte b/src/routes/namespaces/[namespace]/index@root.svelte deleted file mode 100644 index 5f995cb75..000000000 --- a/src/routes/namespaces/[namespace]/index@root.svelte +++ /dev/null @@ -1,131 +0,0 @@ - - - - - -

- Namespace: {namespace?.namespaceInfo?.name} -

-
-
-

Details

-

- Description: - {namespace?.namespaceInfo?.description} -

-

- Owner: - {namespace?.namespaceInfo?.ownerEmail || 'Unknown'} -

-

- Global? - {namespace?.isGlobalNamespace ? 'Yes' : 'No'} -

-

- Retention Period: - {fromSecondsToDaysOrHours( - namespace?.config?.workflowExecutionRetentionTtl.toString(), - )} -

-

- History Archival: - {namespace?.config?.historyArchivalState} -

-

- Visibility Archival: - {namespace?.config?.visibilityArchivalState} -

-

- Failover Version: - {namespace?.failoverVersion} -

-

- Clusters: - {clusters} -

-
- -
-

Versions

-

- Temporal Server Version: - {$temporalVersion} -

-

- Temporal UI Version: - {$uiVersion} -

- {#if dev} -

- Supports Descending Event History: - {$supportsReverseOrder} -

- {/if} -
-
- -{#if $searchAttributes} -
-

Search Attributes

- - - - - - {#each Object.entries($searchAttributes) as [key, type]} - - - - - {/each} -
KeyType{key}{type}
-
-{/if} diff --git a/src/routes/namespaces/[namespace]/schedules/[schedule]/edit.svelte b/src/routes/namespaces/[namespace]/schedules/[schedule]/edit.svelte deleted file mode 100644 index 3bf714a61..000000000 --- a/src/routes/namespaces/[namespace]/schedules/[schedule]/edit.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/src/routes/namespaces/[namespace]/schedules/[schedule]/index.svelte b/src/routes/namespaces/[namespace]/schedules/[schedule]/index.svelte deleted file mode 100644 index 0bd4612c6..000000000 --- a/src/routes/namespaces/[namespace]/schedules/[schedule]/index.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/src/routes/namespaces/[namespace]/schedules/create.svelte b/src/routes/namespaces/[namespace]/schedules/create.svelte deleted file mode 100644 index 81c7ec4c0..000000000 --- a/src/routes/namespaces/[namespace]/schedules/create.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/src/routes/namespaces/[namespace]/schedules/index.svelte b/src/routes/namespaces/[namespace]/schedules/index.svelte deleted file mode 100644 index cd0f5a0e6..000000000 --- a/src/routes/namespaces/[namespace]/schedules/index.svelte +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/src/routes/namespaces/[namespace]/task-queues/[queue].svelte b/src/routes/namespaces/[namespace]/task-queues/[queue].svelte deleted file mode 100644 index 58713cf79..000000000 --- a/src/routes/namespaces/[namespace]/task-queues/[queue].svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/src/routes/namespaces/[namespace]/task-queues/__layout@root.svelte b/src/routes/namespaces/[namespace]/task-queues/__layout@root.svelte deleted file mode 100644 index b460150c4..000000000 --- a/src/routes/namespaces/[namespace]/task-queues/__layout@root.svelte +++ /dev/null @@ -1,4 +0,0 @@ -

- Pollers -

- diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/__layout@root.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/__layout@root.svelte deleted file mode 100644 index 26a772fbe..000000000 --- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/__layout@root.svelte +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/__layout.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/__layout.svelte deleted file mode 100644 index ae732e050..000000000 --- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/__layout.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/compact/index.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/compact/index.svelte deleted file mode 100644 index 3df384357..000000000 --- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/compact/index.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/feed/index.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/feed/index.svelte deleted file mode 100644 index 4ca39b555..000000000 --- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/feed/index.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/index.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/index.svelte deleted file mode 100644 index 4b1284923..000000000 --- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/index.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/json/index.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/json/index.svelte deleted file mode 100644 index 85d1d6a76..000000000 --- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/history/json/index.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/index.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/index.svelte deleted file mode 100644 index 56e2b695a..000000000 --- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/index.svelte +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/pending-activities.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/pending-activities.svelte deleted file mode 100644 index ede031fcb..000000000 --- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/pending-activities.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/query.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/query.svelte deleted file mode 100644 index fcad376e4..000000000 --- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/query.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/stack-trace.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/stack-trace.svelte deleted file mode 100644 index 076f7b0da..000000000 --- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/stack-trace.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/workers.svelte b/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/workers.svelte deleted file mode 100644 index e9b2787d5..000000000 --- a/src/routes/namespaces/[namespace]/workflows/[workflow]/[run]/workers.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/src/routes/namespaces/[namespace]/workflows/index@root.svelte b/src/routes/namespaces/[namespace]/workflows/index@root.svelte deleted file mode 100644 index 58aa21dcc..000000000 --- a/src/routes/namespaces/[namespace]/workflows/index@root.svelte +++ /dev/null @@ -1,15 +0,0 @@ - - - - - diff --git a/src/routes/namespaces/index@root.svelte b/src/routes/namespaces/index@root.svelte deleted file mode 100644 index ae55451f7..000000000 --- a/src/routes/namespaces/index@root.svelte +++ /dev/null @@ -1,41 +0,0 @@ - - - -

Namespaces

-{#if $namespaces?.length > 0} - - - - - - {#each visibleItems as namespace} - - - - {/each} -
Name - {namespace.namespaceInfo.name} -
-
-{:else} - -{/if} diff --git a/src/routes/select-namespace/index@root.svelte b/src/routes/select-namespace/index@root.svelte deleted file mode 100644 index df6eed06e..000000000 --- a/src/routes/select-namespace/index@root.svelte +++ /dev/null @@ -1,88 +0,0 @@ - - - -
-

Welcome to Temporal

-

Select a namespace to get started.

-
-
- -
- -
-
    - {#if namespaceList.length} - {#if filteredList.length} - - - - {:else} - - {/if} - {:else} - - {/if} -
-
- - diff --git a/src/routes/signin/__layout-signin.svelte b/src/routes/signin/__layout-signin.svelte deleted file mode 100644 index c0d32801f..000000000 --- a/src/routes/signin/__layout-signin.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/src/routes/signin/index@signin.svelte b/src/routes/signin/index@signin.svelte deleted file mode 100644 index ad4300acb..000000000 --- a/src/routes/signin/index@signin.svelte +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - -
-

Welcome back.

-

Let's get you signed in.

-
- -
- - {#if error} -
-

- {error} -

-
- {/if} -
diff --git a/src/schedule.d.ts b/src/schedule.d.ts deleted file mode 100644 index 310e297bf..000000000 --- a/src/schedule.d.ts +++ /dev/null @@ -1,69 +0,0 @@ -type DescribeSchedule = import('$types').DescribeScheduleResponse; -type CalendarSpec = import('types').ICalendarSpec; - -type DescribeFullSchedule = DescribeScheduleResponse & { - schedule_id: string; - schedule?: FullScheduleSpec; -}; - -type FullScheduleSpec = Schedule & { - calendar: FullCalendarSpec; -}; - -type FullCalendarSpec = CalendarSpec & { - cronString?: string[]; - structuredCalendar?: any[]; -}; - -type StartEndInterval = { - start?: number; - end?: number; - step?: number; -}; - -type StructuredCalendar = { - second?: StartEndInterval[]; - minute?: StartEndInterval[]; - hour?: StartEndInterval[]; - dayOfMonth?: StartEndInterval[]; - dayOfWeek?: StartEndInterval[]; - month?: StartEndInterval[]; - year?: StartEndInterval[]; - comment?: string; -}; - -type SchedulePreset = 'existing' | 'interval' | 'week' | 'month' | 'string'; - -type ScheduleOffsetUnit = 'days' | 'hrs' | 'min' | 'sec'; - -type ScheduleActionParameters = { - namespace: string; - name: string; - workflowType: string; - workflowId: string; - taskQueue: string; -}; - -type ScheduleSpecParameters = { - dayOfWeek: string; - dayOfMonth: string; - month: string; - hour: string; - minute: string; - second: string; - phase: string; - cronString: string; -}; - -// For UI Only -type SchedulePresetsParameters = { - preset: SchedulePreset; - days: string; - daysOfWeek: string[]; - daysOfMonth: number[]; - months: string[]; -}; - -type ScheduleParameters = ScheduleRequiredParameters & - ScheduleSpecParameters & - ScheduleUISpecParameters; diff --git a/src/search-attributes.d.ts b/src/search-attributes.d.ts deleted file mode 100644 index 631cb7e28..000000000 --- a/src/search-attributes.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -type SearchAttributesValue = - | 'Bool' - | 'Datetime' - | 'Double' - | 'Int' - | 'Keyword' - | 'Text'; - -type SearchAttributes = { - [k: string]: SearchAttributesValue; -}; - -type SearchAttributesResponse = { - keys: SearchAttributes; -}; diff --git a/src/types/index.ts b/src/types/index.ts deleted file mode 100644 index 9fa6ba594..000000000 --- a/src/types/index.ts +++ /dev/null @@ -1,168 +0,0 @@ -import type { google, temporal } from '@temporalio/proto'; - -// api.workflowservice - -export type DescribeNamespaceResponse = - temporal.api.workflowservice.v1.IDescribeNamespaceResponse; -export type DescribeWorkflowExecutionResponse = - temporal.api.workflowservice.v1.IDescribeWorkflowExecutionResponse; -export type ListNamespacesResponse = - temporal.api.workflowservice.v1.IListNamespacesResponse; -export type GetClusterInfoResponse = - temporal.api.workflowservice.v1.IGetClusterInfoResponse; -export type GetWorkflowExecutionHistoryResponse = - temporal.api.workflowservice.v1.IGetWorkflowExecutionHistoryResponse; -export type GetSearchAttributesResponse = - temporal.api.workflowservice.v1.IGetSearchAttributesResponse; -export type ListWorkflowExecutionsResponse = - temporal.api.workflowservice.v1.IListWorkflowExecutionsResponse; -export type ListScheduleResponse = - temporal.api.workflowservice.v1.IListSchedulesResponse; -export type DescribeScheduleResponse = - temporal.api.workflowservice.v1.IDescribeScheduleResponse; -export type Schedule = temporal.api.schedule.v1.ISchedule; -export type CreateScheduleRequest = - temporal.api.workflowservice.v1.ICreateScheduleRequest; -export type PatchScheduleRequest = - temporal.api.workflowservice.v1.IPatchScheduleRequest; -export type UpdateScheduleRequest = - temporal.api.workflowservice.v1.IUpdateScheduleRequest; - -// api.history - -export type History = temporal.api.history.v1.IHistory; -export type HistoryEvent = temporal.api.history.v1.IHistoryEvent; -export type WorkflowExecutionStartedEventAttributes = - temporal.api.history.v1.IWorkflowExecutionStartedEventAttributes; -export type WorkflowExecutionCompletedEventAttributes = - temporal.api.history.v1.IWorkflowExecutionCompletedEventAttributes; -export type WorkflowExecutionFailedEventAttributes = - temporal.api.history.v1.IWorkflowExecutionFailedEventAttributes; -export type WorkflowExecutionTimedOutEventAttributes = - temporal.api.history.v1.IWorkflowExecutionTimedOutEventAttributes; -export type WorkflowTaskScheduledEventAttributes = - temporal.api.history.v1.IWorkflowTaskScheduledEventAttributes; -export type WorkflowTaskStartedEventAttributes = - temporal.api.history.v1.IWorkflowTaskStartedEventAttributes; -export type WorkflowTaskCompletedEventAttributes = - temporal.api.history.v1.IWorkflowTaskCompletedEventAttributes; -export type WorkflowTaskTimedOutEventAttributes = - temporal.api.history.v1.IWorkflowTaskTimedOutEventAttributes; -export type WorkflowTaskFailedEventAttributes = - temporal.api.history.v1.IWorkflowTaskFailedEventAttributes; -export type ActivityTaskScheduledEventAttributes = - temporal.api.history.v1.IActivityTaskScheduledEventAttributes; -export type ActivityTaskStartedEventAttributes = - temporal.api.history.v1.IActivityTaskStartedEventAttributes; -export type ActivityTaskCompletedEventAttributes = - temporal.api.history.v1.IActivityTaskCompletedEventAttributes; -export type ActivityTaskFailedEventAttributes = - temporal.api.history.v1.IActivityTaskFailedEventAttributes; -export type ActivityTaskTimedOutEventAttributes = - temporal.api.history.v1.IActivityTaskTimedOutEventAttributes; -export type TimerStartedEventAttributes = - temporal.api.history.v1.ITimerStartedEventAttributes; -export type TimerFiredEventAttributes = - temporal.api.history.v1.ITimerFiredEventAttributes; -export type ActivityTaskCancelRequestedEventAttributes = - temporal.api.history.v1.IActivityTaskCancelRequestedEventAttributes; -export type ActivityTaskCanceledEventAttributes = - temporal.api.history.v1.IActivityTaskCanceledEventAttributes; -export type TimerCanceledEventAttributes = - temporal.api.history.v1.ITimerCanceledEventAttributes; -export type MarkerRecordedEventAttributes = - temporal.api.history.v1.IMarkerRecordedEventAttributes; -export type WorkflowExecutionSignaledEventAttributes = - temporal.api.history.v1.IWorkflowExecutionSignaledEventAttributes; -export type WorkflowExecutionTerminatedEventAttributes = - temporal.api.history.v1.IWorkflowExecutionTerminatedEventAttributes; -export type WorkflowExecutionCancelRequestedEventAttributes = - temporal.api.history.v1.IWorkflowExecutionCancelRequestedEventAttributes; -export type WorkflowExecutionCanceledEventAttributes = - temporal.api.history.v1.IWorkflowExecutionCanceledEventAttributes; -export type RequestCancelExternalWorkflowExecutionInitiatedEventAttributes = - temporal.api.history.v1.IRequestCancelExternalWorkflowExecutionInitiatedEventAttributes; -export type RequestCancelExternalWorkflowExecutionFailedEventAttributes = - temporal.api.history.v1.IRequestCancelExternalWorkflowExecutionFailedEventAttributes; -export type ExternalWorkflowExecutionCancelRequestedEventAttributes = - temporal.api.history.v1.IExternalWorkflowExecutionCancelRequestedEventAttributes; -export type WorkflowExecutionContinuedAsNewEventAttributes = - temporal.api.history.v1.IWorkflowExecutionContinuedAsNewEventAttributes; -export type StartChildWorkflowExecutionInitiatedEventAttributes = - temporal.api.history.v1.IStartChildWorkflowExecutionInitiatedEventAttributes; -export type StartChildWorkflowExecutionFailedEventAttributes = - temporal.api.history.v1.IStartChildWorkflowExecutionFailedEventAttributes; -export type ChildWorkflowExecutionStartedEventAttributes = - temporal.api.history.v1.IChildWorkflowExecutionStartedEventAttributes; -export type ChildWorkflowExecutionCompletedEventAttributes = - temporal.api.history.v1.IChildWorkflowExecutionCompletedEventAttributes; -export type ChildWorkflowExecutionFailedEventAttributes = - temporal.api.history.v1.IChildWorkflowExecutionFailedEventAttributes; -export type ChildWorkflowExecutionCanceledEventAttributes = - temporal.api.history.v1.IChildWorkflowExecutionCanceledEventAttributes; -export type ChildWorkflowExecutionTimedOutEventAttributes = - temporal.api.history.v1.IChildWorkflowExecutionTimedOutEventAttributes; -export type ChildWorkflowExecutionTerminatedEventAttributes = - temporal.api.history.v1.IChildWorkflowExecutionTerminatedEventAttributes; -export type SignalExternalWorkflowExecutionInitiatedEventAttributes = - temporal.api.history.v1.ISignalExternalWorkflowExecutionInitiatedEventAttributes; -export type SignalExternalWorkflowExecutionFailedEventAttributes = - temporal.api.history.v1.ISignalExternalWorkflowExecutionFailedEventAttributes; -export type ExternalWorkflowExecutionSignaledEventAttributes = - temporal.api.history.v1.IExternalWorkflowExecutionSignaledEventAttributes; -export type UpsertWorkflowSearchAttributesEventAttributes = - temporal.api.history.v1.IUpsertWorkflowSearchAttributesEventAttributes; - -// api.enums - -export type WorkflowExecutionStatus = - temporal.api.enums.v1.WorkflowExecutionStatus; -export type Severity = temporal.api.enums.v1.Severity; -export type WorkflowTaskFailedCause = - temporal.api.enums.v1.WorkflowTaskFailedCause; - -// api.workflow - -export type PendingActivityInfo = temporal.api.workflow.v1.IPendingActivityInfo; -export type PendingChildrenInfo = - temporal.api.workflow.v1.IPendingChildExecutionInfo; - -export type WorkflowExecutionInfo = - temporal.api.workflow.v1.IWorkflowExecutionInfo; - -// api response -export type Payload = temporal.api.common.v1.IPayload; -export type Payloads = temporal.api.common.v1.IPayloads; - -// api.taskqueue - -export type PollerInfo = temporal.api.taskqueue.v1.IPollerInfo; -export type TaskQueueStatus = temporal.api.taskqueue.v1.ITaskQueueStatus; - -// api.schedule - -export type ScheduleListEntry = temporal.api.schedule.v1.IScheduleListEntry; -export type ScheduleSpec = temporal.api.schedule.v1.IScheduleSpec; -export type ScheduleState = temporal.api.schedule.v1.IScheduleState; -export type SchedulePolicies = temporal.api.schedule.v1.ISchedulePolicies; - -export type CalendarSpec = temporal.api.schedule.v1.ICalendarSpec; -export type IntervalSpec = temporal.api.schedule.v1.IIntervalSpec; -export type ScheduleActionResult = - temporal.api.schedule.v1.IScheduleActionResult; - -// google - -export type Timestamp = google.protobuf.ITimestamp; - -// extra APIs -export type SettingsResponse = { - Auth: { Enabled: boolean; Options: string[] }; - Codec: { Endpoint: string; PassAccessToken?: boolean }; - DefaultNamespace: string; - DisableWriteActions: boolean; - ShowTemporalSystemNamespace: boolean; - NotifyOnNewVersion: boolean; - FeedbackURL: string; - Version: string; -}; diff --git a/src/workflow.d.ts b/src/workflow.d.ts deleted file mode 100644 index 972679de0..000000000 --- a/src/workflow.d.ts +++ /dev/null @@ -1,75 +0,0 @@ -type WorkflowExecutionStatus = import('$types').WorkflowExecutionStatus; -type WorkflowTaskFailedCause = import('$types').WorkflowTaskFailedCause; -type ListWorkflowExecutionsResponse = - import('$types').ListWorkflowExecutionsResponse; - -type WorkflowExecutionAPIResponse = Optional< - DescribeWorkflowExecutionResponse, - 'executionConfig' | 'pendingActivities' | 'pendingChildren' ->; - -type WorkflowStatus = - | 'Running' - | 'TimedOut' - | 'Completed' - | 'Failed' - | 'Completed' - | 'ContinuedAsNew' - | 'Canceled' - | 'Terminated' - | null; - -type WorkflowType = string | null; - -type WorkflowExecutionFilters = { - type: WorkflowType; - status: WorkflowStatus; -}; - -type FilterParameters = { - workflowId?: string; - workflowType?: string; - executionStatus?: WorkflowStatus; - timeRange?: Duration | string; - query?: string; -}; - -type ArchiveFilterParameters = Omit & { - closeTime?: Duration | string; -}; - -type WorkflowExecution = { - name: string; - id: string; - runId: string; - startTime: string; - endTime: string; - status: WorkflowStatus; - taskQueue?: string; - historyEvents: Long; - pendingChildren: PendingChildren[]; - pendingActivities: PendingActivity[]; - stateTransitionCount: string; - parentNamespaceId?: string; - parent?: IWorkflowExecution; - url: string; - isRunning: boolean; - defaultWorkflowTaskTimeout: Duration; - canBeTerminated: boolean; -}; - -type BatchOperationType = 'Terminate' | 'Cancel' | 'Signal'; -type BatchOperationStatus = 'Running' | 'Complete' | 'Failed' | 'Unspecified'; - -type BatchOperationInfo = { - operationType: BatchOperationType; - jobId: string; - state: BatchOperationStatus; - startTime: string; - closeTime: string; - totalOperationCount: string; - completeOperationCount: string; - failureOperationCount: string; - identity: string; - reason: string; -}; diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 000000000..277bc4ba6 Binary files /dev/null and b/static/favicon.ico differ diff --git a/svelte.config.js b/svelte.config.js index 95c449ca6..e9de07a37 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -1,52 +1,48 @@ -import preprocess from 'svelte-preprocess'; +import adapter from '@sveltejs/adapter-static'; import vercel from '@sveltejs/adapter-vercel'; +import { sveltePreprocess } from 'svelte-preprocess'; // Workaround until SvelteKit uses Vite 2.3.8 (and it's confirmed to fix the Tailwind JIT problem) const mode = process.env.NODE_ENV; const dev = mode === 'development'; process.env.TAILWIND_MODE = dev ? 'watch' : 'build'; -const buildTarget = process.env.VITE_TEMPORAL_UI_BUILD_TARGET || 'local'; -let outputDirectory = `build-${buildTarget}`; +const ci = !!process.env.VERCEL; -const publicPath = process.env.VITE_PUBLIC_PATH || ''; +const buildPath = process.env.BUILD_PATH || 'build'; /** @type {import('@sveltejs/kit').Config} */ -const config = { +export default { // Consult https://github.com/sveltejs/svelte-preprocess // for more information about preprocessors preprocess: [ - preprocess({ + sveltePreprocess({ postcss: true, }), ], kit: { - adapter: vercel({ - pages: outputDirectory, - assets: outputDirectory, - fallback: 'index.html', - }), - paths: { - base: publicPath, + alias: { + $lib: 'src/lib', + '$lib/*': 'src/lib/*', + $types: 'src/lib/types', + '$types/*': 'src/lib/types/*', + '$components/*': 'src/components/*', + '$fixtures/*': 'src/fixtures/*', }, - package: { - dir: 'package', - emitTypes: true, - // Don't include components for now. - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - exports: (filepath) => { - return /^(layouts|models|pages|services|stores|utilities|holocene)/.test( - filepath, - ); - }, - //eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - files: (filepath) => - /^(?!.*\.(spec|test)\.ts$).*\.(svelte|ts|gif)$/.test(filepath), + + adapter: ci + ? vercel() + : adapter({ + fallback: 'index.html', + pages: buildPath, + assets: buildPath, + }), + prerender: { + entries: [], }, - version: { - pollInterval: 10000, + csp: { + mode: 'auto', + directives: { 'script-src': ['strict-dynamic'] }, }, }, }; - -export default config; diff --git a/tailwind.config.cjs b/tailwind.config.cjs deleted file mode 100644 index b46e6ed9d..000000000 --- a/tailwind.config.cjs +++ /dev/null @@ -1,156 +0,0 @@ -const defaultTheme = require('tailwindcss/defaultTheme'); - -const temporalColors = { - primary: { DEFAULT: '#18181b' }, // gray-900 - secondary: '#64748b', // blue-gray-500 - primaryText: '#141414', - spaceGray: { DEFAULT: '#141414' }, - white: '#ffffff', - offWhite: '#F8F8F7', - danger: '#b91c1c', // red-700 - success: '#bbf7d0', // green-200 - black: '#000000', - lightBlue: '#DFFBFF', - lightPurple: '#DDD6FE', - blue: { - 50: '#eff6ff', - 100: '#dbeafe', - 200: '#bfdbfe', - 300: '#93c5fd', - 400: '#60a5fa', - 500: '#3b82f6', - 600: '#2563eb', - 700: '#1d4ed8', - 800: '#1e40af', - 900: '#1e3a8a', - }, - indigo: { - 50: '#eef2ff', - 100: '#e0e7ff', - 200: '#c7d2fe', - 300: '#a5b4fc', - 400: '#818cf8', - 500: '#6366f1', - 600: '#4f46e5', - 700: '#4338ca', - 800: '#3730a3', - 900: '#312e81', - }, - gray: { - 50: '#fafafa', - 100: '#f4f4f5', - 200: '#e4e4e7', - 300: '#d4d4d8', - 400: '#a1a1aa', - 500: '#71717a', - 600: '#52525b', - 700: '#3f3f46', - 800: '#27272a', - 900: '#18181b', - }, - blueGray: { - 50: '#f8fafc', - 100: '#f1f5f9', - 200: '#e2e8f0', - 300: '#cbd5e1', - 400: '#94a3b8', - 500: '#64748b', - 600: '#475569', - 700: '#334155', - 800: '#1e293b', - 900: '#0f172a', - }, - orange: { - 50: '#fff7ed', - 100: '#ffedd5', - 200: '#fed7aa', - 300: '#fdba74', - 400: '#fb923c', - 500: '#f97316', - 600: '#ea580c', - 700: '#c2410c', - 800: '#9a3412', - 900: '#7c2d12', - }, - yellow: { - 50: '#fefce8', - 100: '#fef9c3', - 200: '#fef08a', - 300: '#fde047', - 400: '#facc15', - 500: '#eab308', - 600: '#ca8a04', - 700: '#a16207', - 800: '#854d0e', - 900: '#713f12', - }, - green: { - 50: '#f0fdf4', - 100: '#dcfce7', - 200: '#bbf7d0', - 300: '#86efac', - 400: '#4ade80', - 500: '#22c55e', - 600: '#16a34a', - 700: '#15803d', - 800: '#166534', - 900: '#14532d', - }, - red: { - 50: '#fef2f2', - 100: '#fee2e2', - 200: '#fecaca', - 300: '#fca5a5', - 400: '#f87171', - 500: '#ef4444', - 600: '#dc2626', - 700: '#b91c1c', - 800: '#991b1b', - 900: '#7f1d1d', - }, - pink: { - 50: '#fdf2f8', - 100: '#fce7f3', - 200: '#fbcfe8', - 300: '#f9a8d4', - 400: '#f472b6', - 500: '#ec4899', - 600: '#db2777', - 700: '#be185d', - 800: '#9d174d', - 900: '#831843', - }, - purple: { - 50: '#faf5ff', - 100: '#f3e8ff', - 200: '#e9d5ff', - 300: '#d8b4fe', - 400: '#c084fc', - 500: '#a855f7', - 600: '#9333ea', - 700: '#7e22ce', - 800: '#6b21a8', - 900: '#581c87', - }, -}; - -const config = { - content: ['./src/**/*.{html,js,svelte,ts}'], - theme: { - colors: temporalColors, - textColor: temporalColors, - backgroundColor: temporalColors, - fontFamily: { - primary: ['Inter', ...defaultTheme.fontFamily.sans], - secondary: ['Poppins', ...defaultTheme.fontFamily.sans], - }, - extend: { - animation: { - 'spin-slow': 'spin 3s linear infinite', - }, - }, - }, - plugins: [], -}; - -module.exports = config; diff --git a/tailwind.config.ts b/tailwind.config.ts new file mode 100644 index 000000000..232824fc7 --- /dev/null +++ b/tailwind.config.ts @@ -0,0 +1,26 @@ +import type { Config } from 'tailwindcss'; +import { fontFamily } from 'tailwindcss/defaultTheme'; + +import temporal, { textStyles } from './src/lib/theme/plugin'; + +const config = { + content: ['./src/**/*.{html,js,svelte,ts}'], + darkMode: ['selector', '[data-theme="dark"]'], + theme: { + fontFamily: { + sans: ['Inter', ...fontFamily.sans], + mono: ['Noto Sans Mono', ...fontFamily.mono], + }, + extend: { + animation: { + 'spin-slow': 'spin 3s linear infinite', + }, + zIndex: { + 100: '100', + }, + }, + }, + plugins: [temporal, textStyles], +} satisfies Config; + +export default config; diff --git a/temporal/activities/double.ts b/temporal/activities/double.ts new file mode 100644 index 000000000..b7ed9db43 --- /dev/null +++ b/temporal/activities/double.ts @@ -0,0 +1,3 @@ +export default async function (value: number): Promise { + return value * 2; +} diff --git a/temporal/activities/echo.ts b/temporal/activities/echo.ts new file mode 100644 index 000000000..525bfbcec --- /dev/null +++ b/temporal/activities/echo.ts @@ -0,0 +1,3 @@ +export default async function (input: string): Promise { + return `Received ${input}`; +} diff --git a/temporal/activities/index.ts b/temporal/activities/index.ts new file mode 100644 index 000000000..eb9f6dccd --- /dev/null +++ b/temporal/activities/index.ts @@ -0,0 +1,2 @@ +export { default as echo } from './echo'; +export { default as double } from './double'; diff --git a/temporal/client.ts b/temporal/client.ts new file mode 100644 index 000000000..1f69d0b40 --- /dev/null +++ b/temporal/client.ts @@ -0,0 +1,73 @@ +import { Client, Connection, type WorkflowHandle } from '@temporalio/client'; + +import { getDataConverter } from './data-converter'; +import { + BlockingWorkflow, + CompletedWorkflow, + RunningWorkflow, + Workflow, +} from './workflows'; + +let connection: Connection; + +export const disconnect = async () => { + if (connection) { + connection.close(); + } +}; + +export const connect = async () => { + connection = await Connection.connect(); + + const client = new Client({ + connection, + dataConverter: getDataConverter(), + }); + + return client; +}; + +const workflows: WorkflowHandle[] = []; + +export const startWorkflows = async ( + client: Client, +): Promise<(string | number | void)[]> => { + const wf1 = await client.workflow.start(Workflow, { + taskQueue: 'e2e-1', + args: ['Plain text input 1'], + workflowId: 'e2e-workflow-1', + }); + + const wf2 = await client.workflow.start(BlockingWorkflow, { + taskQueue: 'e2e-1', + args: ['Plain text input 2'], + workflowId: 'e2e-workflow-2', + }); + + const wf3 = await client.workflow.start(CompletedWorkflow, { + taskQueue: 'e2e-1', + args: [2], + workflowId: 'completed-workflow', + }); + + const wf4 = await client.workflow.start(RunningWorkflow, { + taskQueue: 'e2e-1', + args: [], + workflowId: 'running-workflow', + }); + + workflows.push(wf1, wf2, wf3, wf4); + + return Promise.all([wf1.result(), wf3.result()]); +}; + +export const stopWorkflows = (): Promise => { + return Promise.all( + workflows.map(async (workflow) => { + try { + await workflow.terminate(); + console.log(`🔪 terminated workflow ${workflow.workflowId}`); + } catch {} // eslint-disable-line no-empty + }), + ); +}; diff --git a/temporal/codec-server.ts b/temporal/codec-server.ts new file mode 100644 index 000000000..3d37ab476 --- /dev/null +++ b/temporal/codec-server.ts @@ -0,0 +1,89 @@ +import type { Server } from 'http'; + +import cors from 'cors'; +import type { Application } from 'express'; +import express from 'express'; + +export type CodecServer = { + start: () => Promise; + stop: () => Promise; +}; + +export type CodecServerOptions = { + port?: number; +}; + +interface JSONPayload { + metadata?: Record | null; + data?: string | null; +} + +interface Body { + payloads: JSONPayload[]; +} + +const PORT = 8888; + +let codecServer: CodecServer; +export const getCodecServer = (): CodecServer => codecServer; + +export async function createCodecServer( + { port }: CodecServerOptions = { port: PORT }, +): Promise { + let server: Server; + const app: Application = express(); + + app.use(cors({ allowedHeaders: ['x-namespace', 'content-type'] })); + app.use(express.json()); + + app.post('/encode', async (req, res) => { + try { + const { payloads: raw } = req.body as Body; + res.json({ payloads: raw }).end(); + } catch (err) { + console.error('Error in /encode', err); + res.status(500).end('Internal server error'); + } + }); + + app.post('/decode', async (req, res) => { + try { + const { payloads: raw } = req.body as Body; + res.json({ payloads: raw }).end(); + } catch (err) { + console.error('Error in /decode', err); + res.status(500).end('Internal server error'); + } + }); + + const start = () => + new Promise((resolve, reject) => { + server = app.listen(port, () => { + console.log(`✨ codec server listening on http://127.0.0.1:${port}`); + app.on('error', (error) => { + reject(error); + }); + resolve(server); + }); + }); + + const stop = () => + new Promise((resolve, reject) => { + server.close((error) => { + if (error) { + reject(error); + return; + } + + console.log('🔪 killed codec server'); + resolve(); + }); + }); + + codecServer = { + start, + stop, + }; + + return codecServer; +} diff --git a/temporal/data-converter.ts b/temporal/data-converter.ts new file mode 100644 index 000000000..55d9f0f8f --- /dev/null +++ b/temporal/data-converter.ts @@ -0,0 +1,18 @@ +import type { DataConverter } from '@temporalio/common'; + +import { PayloadCodec } from './payload-codec'; + +let dataConverter: DataConverter; + +export function getDataConverter(): DataConverter { + if (!dataConverter) { + dataConverter = createDataConverter(); + } + return dataConverter; +} + +function createDataConverter(): DataConverter { + return { + payloadCodecs: [new PayloadCodec()], + }; +} diff --git a/temporal/payload-codec.ts b/temporal/payload-codec.ts new file mode 100644 index 000000000..4c4fab173 --- /dev/null +++ b/temporal/payload-codec.ts @@ -0,0 +1,34 @@ +import type { + Payload, + PayloadCodec as TPayloadCodec, +} from '@temporalio/common'; + +export class PayloadCodec implements TPayloadCodec { + async encode(payloads: Payload[]): Promise { + return payloads; + // return Promise.all( + // payloads.map(async (payload) => { + // return { + // metadata: { + // [METADATA_ENCODING_KEY]: encode(ENCODING), + // }, + // data: payload.data, + // }; + // }), + // ); + } + + async decode(payloads: Payload[]): Promise { + return payloads; + // return Promise.all( + // payloads.map(async (payload) => { + // return { + // metadata: { + // [METADATA_ENCODING_KEY]: encode(ENCODING), + // }, + // data: payload.data, + // }; + // }), + // ); + } +} diff --git a/temporal/workers.ts b/temporal/workers.ts new file mode 100644 index 000000000..5e39428a3 --- /dev/null +++ b/temporal/workers.ts @@ -0,0 +1,69 @@ +import { createRequire } from 'node:module'; + +import { DefaultLogger, Runtime, Worker } from '@temporalio/worker'; + +// eslint-disable-next-line @typescript-eslint/no-empty-function +const logger = new DefaultLogger('ERROR', () => {}); + +Runtime.install({ logger }); + +import * as activities from './activities/index'; +import { getDataConverter } from './data-converter'; + +const require = createRequire(import.meta.url); + +let worker: Worker; + +const createWorker = async (): Promise => { + return Worker.create({ + dataConverter: getDataConverter(), + workflowsPath: require.resolve('./workflows'), + activities, + taskQueue: 'e2e-1', + }); +}; + +const workerIsRunning = () => { + return worker.getState() === 'RUNNING'; +}; + +const workerIsStopped = () => { + return worker.getState() === 'STOPPED'; +}; + +export const runWorker = async (): Promise => { + worker = await createWorker(); + worker.run(); + + return new Promise((resolve) => { + (function waitForWorkerToBeRunning() { + if (workerIsRunning()) { + console.log('✨ temporal worker is running'); + return resolve(); + } + setTimeout(waitForWorkerToBeRunning, 100); + })(); + }); +}; + +export const runWorkerUntil = async (completed: Promise) => { + worker = await createWorker(); + + return worker.runUntil(completed); +}; + +export const stopWorker = async (): Promise => { + if (worker && workerIsRunning()) { + worker.shutdown(); + + return new Promise((resolve) => { + (function waitForWorkerToBeStopped() { + if (workerIsStopped()) { + console.log('🔪 killed temporal worker'); + return resolve(); + } + setTimeout(waitForWorkerToBeStopped, 1000); + })(); + }); + } +}; diff --git a/temporal/workflows.ts b/temporal/workflows.ts new file mode 100644 index 000000000..e27d5b757 --- /dev/null +++ b/temporal/workflows.ts @@ -0,0 +1,65 @@ +import * as workflow from '@temporalio/workflow'; + +import type * as activities from './activities'; + +const { echo: Activity } = workflow.proxyActivities({ + startToCloseTimeout: '10 seconds', +}); + +const { echo: LocalActivity } = workflow.proxyLocalActivities< + typeof activities +>({ + startToCloseTimeout: '10 seconds', +}); + +const isBlockedQuery = workflow.defineQuery('is-blocked'); +const unblockSignal = workflow.defineSignal('unblock'); + +const { double } = workflow.proxyActivities({ + startToCloseTimeout: '1 hour', + retry: { + maximumAttempts: 1, + }, +}); + +export async function Workflow(input: string): Promise { + let result: string; + + result = await LocalActivity(input); + result = await Activity(input); + + return result; +} + +export async function BlockingWorkflow(input: string): Promise { + let isBlocked = true; + + workflow.setHandler(unblockSignal, () => void (isBlocked = false)); + workflow.setHandler(isBlockedQuery, () => isBlocked); + + try { + await workflow.condition(() => !isBlocked); + } catch (err) { + if (err instanceof workflow.CancelledFailure) { + console.log('Cancelled'); + } + throw err; + } + + return Activity(input); +} + +export async function CompletedWorkflow( + amount: number, + iterations = 0, +): Promise { + if (iterations) { + await workflow.continueAsNew(amount, iterations - 1); + } + + return await double(amount); +} + +export async function RunningWorkflow(): Promise { + return await workflow.sleep('10 days'); +} diff --git a/tests/accessibility/empty-states.accessibility.spec.ts b/tests/accessibility/empty-states.accessibility.spec.ts new file mode 100644 index 000000000..0f8f897c9 --- /dev/null +++ b/tests/accessibility/empty-states.accessibility.spec.ts @@ -0,0 +1,51 @@ +import AxeBuilder from '@axe-core/playwright'; +import { expect, test } from '@playwright/test'; + +import { attachViolations } from '~/test-utilities/attach-violations'; + +test.beforeEach(async ({ page }) => { + await page.routeFromHAR( + './tests/accessibility/network-requests/empty-states.har', + { + updateMode: 'minimal', + update: false, + updateContent: 'embed', + notFound: 'fallback', + url: '**/api/v1/**', + }, + ); +}); + +const pages = [ + { title: 'Namespaces', url: '/namespaces' }, + { title: 'Select Namespace', url: '/select-namespace' }, + { title: 'Namespace', url: '/namespaces/default' }, + { title: 'Workflow List', url: '/namespaces/default/workflows' }, + { title: 'Schedules', url: '/namespaces/default/schedules' }, + { title: 'Create Schedule', url: '/namespaces/default/schedules/create' }, + { + title: 'Archived Workflows', + url: 'namespaces/default/archival', + }, + { title: 'Event Import', url: 'import/events' }, +]; + +test.describe('Accessibility: Empty States', () => { + for (const { title, url } of pages) { + test(`${title} page (${url}) should not have any automatically detectable accessibility issues`, async ({ + page, + }, testInfo) => { + await page.goto(url); + await page.waitForRequest('**/api/v1/**'); + await page.waitForSelector('#content', { state: 'visible' }); + + const accessibilityScanResults = await new AxeBuilder({ + page, + }).analyze(); + + await attachViolations(testInfo, accessibilityScanResults, page); + + expect(accessibilityScanResults.violations).toEqual([]); + }); + } +}); diff --git a/tests/accessibility/network-requests/empty-states.har b/tests/accessibility/network-requests/empty-states.har new file mode 100644 index 000000000..4078817fb --- /dev/null +++ b/tests/accessibility/network-requests/empty-states.har @@ -0,0 +1,551 @@ +{ + "log": { + "version": "1.2", + "creator": { + "name": "Playwright", + "version": "1.34.3" + }, + "browser": { + "name": "chromium", + "version": "114.0.5735.35" + }, + "entries": [ + { + "startedDateTime": "2023-06-07T13:18:44.134Z", + "time": 0.914, + "request": { + "method": "GET", + "url": "http://localhost:8233/api/v1/settings?", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br" + }, + { + "name": "Accept-Language", + "value": "en-US" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Host", + "value": "localhost:8233" + }, + { + "name": "Origin", + "value": "http://localhost:3333" + }, + { + "name": "Referer", + "value": "http://localhost:3333/" + }, + { + "name": "Sec-Fetch-Dest", + "value": "empty" + }, + { + "name": "Sec-Fetch-Mode", + "value": "cors" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-site" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.35 Safari/537.36" + } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "http://localhost:3333" + }, + { + "name": "Content-Length", + "value": "456" + }, + { + "name": "Content-Type", + "value": "application/json; charset=UTF-8" + }, + { + "name": "Date", + "value": "Wed, 07 Jun 2023 13:18:44 GMT" + }, + { + "name": "Set-Cookie", + "value": "_csrf=yfLSXSrGc9iby3aRgXrxl33MKdruCRtL; Path=/; Expires=Thu, 08 Jun 2023 13:18:44 GMT; Secure; SameSite=Strict" + }, + { + "name": "Vary", + "value": "Origin" + }, + { + "name": "Vary", + "value": "Cookie" + }, + { + "name": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "name": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "name": "X-Xss-Protection", + "value": "1; mode=block" + } + ], + "content": { + "size": -1, + "mimeType": "application/json; charset=UTF-8", + "text": "{\"Auth\":{\"Enabled\":false,\"Options\":null},\"DefaultNamespace\":\"\",\"ShowTemporalSystemNamespace\":false,\"FeedbackURL\":\"\",\"NotifyOnNewVersion\":false,\"Codec\":{\"Endpoint\":\"\",\"PassAccessToken\":false,\"IncludeCredentials\":false,\"DecodeEventHistoryDownload\":false},\"Version\":\"2.15.0\",\"DisableWriteActions\":false,\"WorkflowTerminateDisabled\":false,\"WorkflowCancelDisabled\":false,\"WorkflowSignalDisabled\":false,\"WorkflowResetDisabled\":false,\"BatchActionsDisabled\":false}\n" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": 0.914 + } + }, + { + "startedDateTime": "2023-06-07T13:18:44.154Z", + "time": 7.273, + "request": { + "method": "GET", + "url": "http://localhost:8233/api/v1/namespaces?", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br" + }, + { + "name": "Accept-Language", + "value": "en-US" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Cookie", + "value": "_csrf=yfLSXSrGc9iby3aRgXrxl33MKdruCRtL" + }, + { + "name": "Host", + "value": "localhost:8233" + }, + { + "name": "Origin", + "value": "http://localhost:3333" + }, + { + "name": "Referer", + "value": "http://localhost:3333/" + }, + { + "name": "Sec-Fetch-Dest", + "value": "empty" + }, + { + "name": "Sec-Fetch-Mode", + "value": "cors" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-site" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.35 Safari/537.36" + }, + { + "name": "X-CSRF-TOKEN", + "value": "yfLSXSrGc9iby3aRgXrxl33MKdruCRtL" + } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "http://localhost:3333" + }, + { + "name": "Content-Length", + "value": "1995" + }, + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Date", + "value": "Wed, 07 Jun 2023 13:18:44 GMT" + }, + { + "name": "Grpc-Metadata-Content-Type", + "value": "application/grpc" + }, + { + "name": "Set-Cookie", + "value": "_csrf=yfLSXSrGc9iby3aRgXrxl33MKdruCRtL; Path=/; Expires=Thu, 08 Jun 2023 13:18:44 GMT; Secure; SameSite=Strict" + }, + { + "name": "Vary", + "value": "Origin" + }, + { + "name": "Vary", + "value": "Cookie" + }, + { + "name": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "name": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "name": "X-Xss-Protection", + "value": "1; mode=block" + } + ], + "content": { + "size": -1, + "mimeType": "application/json", + "text": "{\n \"namespaces\": [\n {\n \"namespaceInfo\": {\n \"name\": \"temporal-system\",\n \"state\": \"Registered\",\n \"description\": \"Temporal internal system namespace\",\n \"ownerEmail\": \"temporal-core@temporal.io\",\n \"data\": {\n },\n \"id\": \"32049b68-7872-4094-8e63-d0dd59896a83\",\n \"supportsSchedules\": true\n },\n \"config\": {\n \"workflowExecutionRetentionTtl\": \"604800s\",\n \"badBinaries\": {\n \"binaries\": {\n }\n },\n \"historyArchivalState\": \"Disabled\",\n \"historyArchivalUri\": \"\",\n \"visibilityArchivalState\": \"Disabled\",\n \"visibilityArchivalUri\": \"\",\n \"customSearchAttributeAliases\": {\n }\n },\n \"replicationConfig\": {\n \"activeClusterName\": \"active\",\n \"clusters\": [\n {\n \"clusterName\": \"active\"\n }\n ],\n \"state\": \"Unspecified\"\n },\n \"failoverVersion\": \"0\",\n \"isGlobalNamespace\": false,\n \"failoverHistory\": [\n ]\n },\n {\n \"namespaceInfo\": {\n \"name\": \"default\",\n \"state\": \"Registered\",\n \"description\": \"\",\n \"ownerEmail\": \"\",\n \"data\": {\n },\n \"id\": \"5b44a925-15e6-46d6-a77d-b53a7668e2d4\",\n \"supportsSchedules\": true\n },\n \"config\": {\n \"workflowExecutionRetentionTtl\": \"86400s\",\n \"badBinaries\": {\n \"binaries\": {\n }\n },\n \"historyArchivalState\": \"Disabled\",\n \"historyArchivalUri\": \"\",\n \"visibilityArchivalState\": \"Disabled\",\n \"visibilityArchivalUri\": \"\",\n \"customSearchAttributeAliases\": {\n }\n },\n \"replicationConfig\": {\n \"activeClusterName\": \"active\",\n \"clusters\": [\n {\n \"clusterName\": \"active\"\n }\n ],\n \"state\": \"Unspecified\"\n },\n \"failoverVersion\": \"0\",\n \"isGlobalNamespace\": false,\n \"failoverHistory\": [\n ]\n }\n ],\n \"nextPageToken\": null\n}" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": 7.273 + } + }, + { + "startedDateTime": "2023-06-07T13:18:44.154Z", + "time": 7.196, + "request": { + "method": "GET", + "url": "http://localhost:8233/api/v1/search-attributes?", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br" + }, + { + "name": "Accept-Language", + "value": "en-US" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Cookie", + "value": "_csrf=yfLSXSrGc9iby3aRgXrxl33MKdruCRtL" + }, + { + "name": "Host", + "value": "localhost:8233" + }, + { + "name": "Origin", + "value": "http://localhost:3333" + }, + { + "name": "Referer", + "value": "http://localhost:3333/" + }, + { + "name": "Sec-Fetch-Dest", + "value": "empty" + }, + { + "name": "Sec-Fetch-Mode", + "value": "cors" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-site" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.35 Safari/537.36" + }, + { + "name": "X-CSRF-TOKEN", + "value": "yfLSXSrGc9iby3aRgXrxl33MKdruCRtL" + } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "http://localhost:3333" + }, + { + "name": "Content-Length", + "value": "1444" + }, + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Date", + "value": "Wed, 07 Jun 2023 13:18:44 GMT" + }, + { + "name": "Grpc-Metadata-Content-Type", + "value": "application/grpc" + }, + { + "name": "Set-Cookie", + "value": "_csrf=yfLSXSrGc9iby3aRgXrxl33MKdruCRtL; Path=/; Expires=Thu, 08 Jun 2023 13:18:44 GMT; Secure; SameSite=Strict" + }, + { + "name": "Vary", + "value": "Origin" + }, + { + "name": "Vary", + "value": "Cookie" + }, + { + "name": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "name": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "name": "X-Xss-Protection", + "value": "1; mode=block" + } + ], + "content": { + "size": -1, + "mimeType": "application/json", + "text": "{\n \"keys\": {\n \"BatcherNamespace\": \"Keyword\",\n \"BatcherUser\": \"Keyword\",\n \"BinaryChecksums\": \"KeywordList\",\n \"Bool01\": \"Bool\",\n \"Bool02\": \"Bool\",\n \"Bool03\": \"Bool\",\n \"CloseTime\": \"Datetime\",\n \"Datetime01\": \"Datetime\",\n \"Datetime02\": \"Datetime\",\n \"Datetime03\": \"Datetime\",\n \"Double01\": \"Double\",\n \"Double02\": \"Double\",\n \"Double03\": \"Double\",\n \"ExecutionDuration\": \"Int\",\n \"ExecutionStatus\": \"Keyword\",\n \"ExecutionTime\": \"Datetime\",\n \"HistoryLength\": \"Int\",\n \"HistorySizeBytes\": \"Int\",\n \"Int01\": \"Int\",\n \"Int02\": \"Int\",\n \"Int03\": \"Int\",\n \"Keyword01\": \"Keyword\",\n \"Keyword02\": \"Keyword\",\n \"Keyword03\": \"Keyword\",\n \"Keyword04\": \"Keyword\",\n \"Keyword05\": \"Keyword\",\n \"Keyword06\": \"Keyword\",\n \"Keyword07\": \"Keyword\",\n \"Keyword08\": \"Keyword\",\n \"Keyword09\": \"Keyword\",\n \"Keyword10\": \"Keyword\",\n \"KeywordList01\": \"KeywordList\",\n \"KeywordList02\": \"KeywordList\",\n \"KeywordList03\": \"KeywordList\",\n \"RunId\": \"Keyword\",\n \"StartTime\": \"Datetime\",\n \"StateTransitionCount\": \"Int\",\n \"TaskQueue\": \"Keyword\",\n \"TemporalChangeVersion\": \"KeywordList\",\n \"TemporalNamespaceDivision\": \"Keyword\",\n \"TemporalSchedulePaused\": \"Bool\",\n \"TemporalScheduledById\": \"Keyword\",\n \"TemporalScheduledStartTime\": \"Datetime\",\n \"Text01\": \"Text\",\n \"Text02\": \"Text\",\n \"Text03\": \"Text\",\n \"WorkflowId\": \"Keyword\",\n \"WorkflowType\": \"Keyword\"\n }\n}" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": 7.196 + } + }, + { + "startedDateTime": "2023-06-07T13:18:44.155Z", + "time": 7.189, + "request": { + "method": "GET", + "url": "http://localhost:8233/api/v1/cluster-info?", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br" + }, + { + "name": "Accept-Language", + "value": "en-US" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Cookie", + "value": "_csrf=yfLSXSrGc9iby3aRgXrxl33MKdruCRtL" + }, + { + "name": "Host", + "value": "localhost:8233" + }, + { + "name": "Origin", + "value": "http://localhost:3333" + }, + { + "name": "Referer", + "value": "http://localhost:3333/" + }, + { + "name": "Sec-Fetch-Dest", + "value": "empty" + }, + { + "name": "Sec-Fetch-Mode", + "value": "cors" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-site" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.35 Safari/537.36" + }, + { + "name": "X-CSRF-TOKEN", + "value": "yfLSXSrGc9iby3aRgXrxl33MKdruCRtL" + } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "http://localhost:3333" + }, + { + "name": "Content-Length", + "value": "936" + }, + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Date", + "value": "Wed, 07 Jun 2023 13:18:44 GMT" + }, + { + "name": "Grpc-Metadata-Content-Type", + "value": "application/grpc" + }, + { + "name": "Set-Cookie", + "value": "_csrf=yfLSXSrGc9iby3aRgXrxl33MKdruCRtL; Path=/; Expires=Thu, 08 Jun 2023 13:18:44 GMT; Secure; SameSite=Strict" + }, + { + "name": "Vary", + "value": "Origin" + }, + { + "name": "Vary", + "value": "Cookie" + }, + { + "name": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "name": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "name": "X-Xss-Protection", + "value": "1; mode=block" + } + ], + "content": { + "size": -1, + "mimeType": "application/json", + "text": "{\n \"supportedClients\": {\n \"temporal-cli\": \"\\u003c2.0.0\",\n \"temporal-go\": \"\\u003c2.0.0\",\n \"temporal-java\": \"\\u003c2.0.0\",\n \"temporal-php\": \"\\u003c2.0.0\",\n \"temporal-server\": \"\\u003c2.0.0\",\n \"temporal-typescript\": \"\\u003c2.0.0\",\n \"temporal-ui\": \"\\u003c3.0.0\"\n },\n \"serverVersion\": \"1.20.2\",\n \"clusterId\": \"f9c299e0-a75f-4306-bfd6-17022bdb56b5\",\n \"versionInfo\": {\n \"current\": {\n \"version\": \"1.20.2\",\n \"releaseTime\": \"2023-04-20T18:15:00Z\",\n \"notes\": \"\"\n },\n \"recommended\": {\n \"version\": \"1.20.2\",\n \"releaseTime\": \"2023-04-20T18:15:00Z\",\n \"notes\": \"\"\n },\n \"instructions\": \"\",\n \"alerts\": [\n {\n \"message\": \"🪐 A new release is available!\",\n \"severity\": \"Low\"\n }\n ],\n \"lastUpdateTime\": \"2023-06-07T13:17:39.683525Z\"\n },\n \"clusterName\": \"active\",\n \"historyShardCount\": 1,\n \"persistenceStore\": \"sqlite\",\n \"visibilityStore\": \"sqlite\"\n}" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": 7.189 + } + } + ] + } +} \ No newline at end of file diff --git a/tests/accessibility/network-requests/with-schedules.har b/tests/accessibility/network-requests/with-schedules.har new file mode 100644 index 000000000..f4e4d65b9 --- /dev/null +++ b/tests/accessibility/network-requests/with-schedules.har @@ -0,0 +1,688 @@ +{ + "log": { + "version": "1.2", + "creator": { + "name": "Playwright", + "version": "1.34.3" + }, + "browser": { + "name": "chromium", + "version": "114.0.5735.35" + }, + "entries": [ + { + "startedDateTime": "2023-06-07T13:04:02.894Z", + "time": 0.595, + "request": { + "method": "GET", + "url": "http://localhost:8233/api/v1/settings?", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br" + }, + { + "name": "Accept-Language", + "value": "en-US" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Host", + "value": "localhost:8233" + }, + { + "name": "Origin", + "value": "http://localhost:3333" + }, + { + "name": "Referer", + "value": "http://localhost:3333/" + }, + { + "name": "Sec-Fetch-Dest", + "value": "empty" + }, + { + "name": "Sec-Fetch-Mode", + "value": "cors" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-site" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.35 Safari/537.36" + } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "http://localhost:3333" + }, + { + "name": "Content-Length", + "value": "456" + }, + { + "name": "Content-Type", + "value": "application/json; charset=UTF-8" + }, + { + "name": "Date", + "value": "Wed, 07 Jun 2023 13:04:02 GMT" + }, + { + "name": "Set-Cookie", + "value": "_csrf=h5fpdnD9Rw3BWBkyO6Lf2eTrjPmhLMlR; Path=/; Expires=Thu, 08 Jun 2023 13:04:02 GMT; Secure; SameSite=Strict" + }, + { + "name": "Vary", + "value": "Origin" + }, + { + "name": "Vary", + "value": "Cookie" + }, + { + "name": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "name": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "name": "X-Xss-Protection", + "value": "1; mode=block" + } + ], + "content": { + "size": -1, + "mimeType": "application/json; charset=UTF-8", + "text": "{\"Auth\":{\"Enabled\":false,\"Options\":null},\"DefaultNamespace\":\"\",\"ShowTemporalSystemNamespace\":false,\"FeedbackURL\":\"\",\"NotifyOnNewVersion\":false,\"Codec\":{\"Endpoint\":\"\",\"PassAccessToken\":false,\"IncludeCredentials\":false,\"DecodeEventHistoryDownload\":false},\"Version\":\"2.15.0\",\"DisableWriteActions\":false,\"WorkflowTerminateDisabled\":false,\"WorkflowCancelDisabled\":false,\"WorkflowSignalDisabled\":false,\"WorkflowResetDisabled\":false,\"BatchActionsDisabled\":false}\n" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": 0.595 + } + }, + { + "startedDateTime": "2023-06-07T13:04:02.897Z", + "time": 3.711, + "request": { + "method": "GET", + "url": "http://localhost:8233/api/v1/namespaces?", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br" + }, + { + "name": "Accept-Language", + "value": "en-US" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Cookie", + "value": "_csrf=h5fpdnD9Rw3BWBkyO6Lf2eTrjPmhLMlR" + }, + { + "name": "Host", + "value": "localhost:8233" + }, + { + "name": "Origin", + "value": "http://localhost:3333" + }, + { + "name": "Referer", + "value": "http://localhost:3333/" + }, + { + "name": "Sec-Fetch-Dest", + "value": "empty" + }, + { + "name": "Sec-Fetch-Mode", + "value": "cors" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-site" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.35 Safari/537.36" + }, + { + "name": "X-CSRF-TOKEN", + "value": "h5fpdnD9Rw3BWBkyO6Lf2eTrjPmhLMlR" + } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "http://localhost:3333" + }, + { + "name": "Content-Length", + "value": "1995" + }, + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Date", + "value": "Wed, 07 Jun 2023 13:04:02 GMT" + }, + { + "name": "Grpc-Metadata-Content-Type", + "value": "application/grpc" + }, + { + "name": "Set-Cookie", + "value": "_csrf=h5fpdnD9Rw3BWBkyO6Lf2eTrjPmhLMlR; Path=/; Expires=Thu, 08 Jun 2023 13:04:02 GMT; Secure; SameSite=Strict" + }, + { + "name": "Vary", + "value": "Origin" + }, + { + "name": "Vary", + "value": "Cookie" + }, + { + "name": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "name": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "name": "X-Xss-Protection", + "value": "1; mode=block" + } + ], + "content": { + "size": -1, + "mimeType": "application/json", + "text": "{\n \"namespaces\": [\n {\n \"namespaceInfo\": {\n \"name\": \"temporal-system\",\n \"state\": \"Registered\",\n \"description\": \"Temporal internal system namespace\",\n \"ownerEmail\": \"temporal-core@temporal.io\",\n \"data\": {\n },\n \"id\": \"32049b68-7872-4094-8e63-d0dd59896a83\",\n \"supportsSchedules\": true\n },\n \"config\": {\n \"workflowExecutionRetentionTtl\": \"604800s\",\n \"badBinaries\": {\n \"binaries\": {\n }\n },\n \"historyArchivalState\": \"Disabled\",\n \"historyArchivalUri\": \"\",\n \"visibilityArchivalState\": \"Disabled\",\n \"visibilityArchivalUri\": \"\",\n \"customSearchAttributeAliases\": {\n }\n },\n \"replicationConfig\": {\n \"activeClusterName\": \"active\",\n \"clusters\": [\n {\n \"clusterName\": \"active\"\n }\n ],\n \"state\": \"Unspecified\"\n },\n \"failoverVersion\": \"0\",\n \"isGlobalNamespace\": false,\n \"failoverHistory\": [\n ]\n },\n {\n \"namespaceInfo\": {\n \"name\": \"default\",\n \"state\": \"Registered\",\n \"description\": \"\",\n \"ownerEmail\": \"\",\n \"data\": {\n },\n \"id\": \"6e5cb7cc-4fed-4491-89c3-bb9b651845ea\",\n \"supportsSchedules\": true\n },\n \"config\": {\n \"workflowExecutionRetentionTtl\": \"86400s\",\n \"badBinaries\": {\n \"binaries\": {\n }\n },\n \"historyArchivalState\": \"Disabled\",\n \"historyArchivalUri\": \"\",\n \"visibilityArchivalState\": \"Disabled\",\n \"visibilityArchivalUri\": \"\",\n \"customSearchAttributeAliases\": {\n }\n },\n \"replicationConfig\": {\n \"activeClusterName\": \"active\",\n \"clusters\": [\n {\n \"clusterName\": \"active\"\n }\n ],\n \"state\": \"Unspecified\"\n },\n \"failoverVersion\": \"0\",\n \"isGlobalNamespace\": false,\n \"failoverHistory\": [\n ]\n }\n ],\n \"nextPageToken\": null\n}" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": 3.711 + } + }, + { + "startedDateTime": "2023-06-07T13:04:02.897Z", + "time": 3.078, + "request": { + "method": "GET", + "url": "http://localhost:8233/api/v1/search-attributes?", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br" + }, + { + "name": "Accept-Language", + "value": "en-US" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Cookie", + "value": "_csrf=h5fpdnD9Rw3BWBkyO6Lf2eTrjPmhLMlR" + }, + { + "name": "Host", + "value": "localhost:8233" + }, + { + "name": "Origin", + "value": "http://localhost:3333" + }, + { + "name": "Referer", + "value": "http://localhost:3333/" + }, + { + "name": "Sec-Fetch-Dest", + "value": "empty" + }, + { + "name": "Sec-Fetch-Mode", + "value": "cors" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-site" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.35 Safari/537.36" + }, + { + "name": "X-CSRF-TOKEN", + "value": "h5fpdnD9Rw3BWBkyO6Lf2eTrjPmhLMlR" + } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "http://localhost:3333" + }, + { + "name": "Content-Length", + "value": "1444" + }, + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Date", + "value": "Wed, 07 Jun 2023 13:04:02 GMT" + }, + { + "name": "Grpc-Metadata-Content-Type", + "value": "application/grpc" + }, + { + "name": "Set-Cookie", + "value": "_csrf=h5fpdnD9Rw3BWBkyO6Lf2eTrjPmhLMlR; Path=/; Expires=Thu, 08 Jun 2023 13:04:02 GMT; Secure; SameSite=Strict" + }, + { + "name": "Vary", + "value": "Origin" + }, + { + "name": "Vary", + "value": "Cookie" + }, + { + "name": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "name": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "name": "X-Xss-Protection", + "value": "1; mode=block" + } + ], + "content": { + "size": -1, + "mimeType": "application/json", + "text": "{\n \"keys\": {\n \"BatcherNamespace\": \"Keyword\",\n \"BatcherUser\": \"Keyword\",\n \"BinaryChecksums\": \"KeywordList\",\n \"Bool01\": \"Bool\",\n \"Bool02\": \"Bool\",\n \"Bool03\": \"Bool\",\n \"CloseTime\": \"Datetime\",\n \"Datetime01\": \"Datetime\",\n \"Datetime02\": \"Datetime\",\n \"Datetime03\": \"Datetime\",\n \"Double01\": \"Double\",\n \"Double02\": \"Double\",\n \"Double03\": \"Double\",\n \"ExecutionDuration\": \"Int\",\n \"ExecutionStatus\": \"Keyword\",\n \"ExecutionTime\": \"Datetime\",\n \"HistoryLength\": \"Int\",\n \"HistorySizeBytes\": \"Int\",\n \"Int01\": \"Int\",\n \"Int02\": \"Int\",\n \"Int03\": \"Int\",\n \"Keyword01\": \"Keyword\",\n \"Keyword02\": \"Keyword\",\n \"Keyword03\": \"Keyword\",\n \"Keyword04\": \"Keyword\",\n \"Keyword05\": \"Keyword\",\n \"Keyword06\": \"Keyword\",\n \"Keyword07\": \"Keyword\",\n \"Keyword08\": \"Keyword\",\n \"Keyword09\": \"Keyword\",\n \"Keyword10\": \"Keyword\",\n \"KeywordList01\": \"KeywordList\",\n \"KeywordList02\": \"KeywordList\",\n \"KeywordList03\": \"KeywordList\",\n \"RunId\": \"Keyword\",\n \"StartTime\": \"Datetime\",\n \"StateTransitionCount\": \"Int\",\n \"TaskQueue\": \"Keyword\",\n \"TemporalChangeVersion\": \"KeywordList\",\n \"TemporalNamespaceDivision\": \"Keyword\",\n \"TemporalSchedulePaused\": \"Bool\",\n \"TemporalScheduledById\": \"Keyword\",\n \"TemporalScheduledStartTime\": \"Datetime\",\n \"Text01\": \"Text\",\n \"Text02\": \"Text\",\n \"Text03\": \"Text\",\n \"WorkflowId\": \"Keyword\",\n \"WorkflowType\": \"Keyword\"\n }\n}" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": 3.078 + } + }, + { + "startedDateTime": "2023-06-07T13:04:02.897Z", + "time": 2.645, + "request": { + "method": "GET", + "url": "http://localhost:8233/api/v1/cluster-info?", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br" + }, + { + "name": "Accept-Language", + "value": "en-US" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Cookie", + "value": "_csrf=h5fpdnD9Rw3BWBkyO6Lf2eTrjPmhLMlR" + }, + { + "name": "Host", + "value": "localhost:8233" + }, + { + "name": "Origin", + "value": "http://localhost:3333" + }, + { + "name": "Referer", + "value": "http://localhost:3333/" + }, + { + "name": "Sec-Fetch-Dest", + "value": "empty" + }, + { + "name": "Sec-Fetch-Mode", + "value": "cors" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-site" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.35 Safari/537.36" + }, + { + "name": "X-CSRF-TOKEN", + "value": "h5fpdnD9Rw3BWBkyO6Lf2eTrjPmhLMlR" + } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "http://localhost:3333" + }, + { + "name": "Content-Length", + "value": "936" + }, + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Date", + "value": "Wed, 07 Jun 2023 13:04:02 GMT" + }, + { + "name": "Grpc-Metadata-Content-Type", + "value": "application/grpc" + }, + { + "name": "Set-Cookie", + "value": "_csrf=h5fpdnD9Rw3BWBkyO6Lf2eTrjPmhLMlR; Path=/; Expires=Thu, 08 Jun 2023 13:04:02 GMT; Secure; SameSite=Strict" + }, + { + "name": "Vary", + "value": "Origin" + }, + { + "name": "Vary", + "value": "Cookie" + }, + { + "name": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "name": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "name": "X-Xss-Protection", + "value": "1; mode=block" + } + ], + "content": { + "size": -1, + "mimeType": "application/json", + "text": "{\n \"supportedClients\": {\n \"temporal-cli\": \"\\u003c2.0.0\",\n \"temporal-go\": \"\\u003c2.0.0\",\n \"temporal-java\": \"\\u003c2.0.0\",\n \"temporal-php\": \"\\u003c2.0.0\",\n \"temporal-server\": \"\\u003c2.0.0\",\n \"temporal-typescript\": \"\\u003c2.0.0\",\n \"temporal-ui\": \"\\u003c3.0.0\"\n },\n \"serverVersion\": \"1.20.2\",\n \"clusterId\": \"f9c299e0-a75f-4306-bfd6-17022bdb56b5\",\n \"versionInfo\": {\n \"current\": {\n \"version\": \"1.20.2\",\n \"releaseTime\": \"2023-04-20T18:15:00Z\",\n \"notes\": \"\"\n },\n \"recommended\": {\n \"version\": \"1.20.2\",\n \"releaseTime\": \"2023-04-20T18:15:00Z\",\n \"notes\": \"\"\n },\n \"instructions\": \"\",\n \"alerts\": [\n {\n \"message\": \"🪐 A new release is available!\",\n \"severity\": \"Low\"\n }\n ],\n \"lastUpdateTime\": \"2023-06-07T13:00:52.277469Z\"\n },\n \"clusterName\": \"active\",\n \"historyShardCount\": 1,\n \"persistenceStore\": \"sqlite\",\n \"visibilityStore\": \"sqlite\"\n}" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": 2.645 + } + }, + { + "startedDateTime": "2023-06-07T13:04:02.941Z", + "time": 1.062, + "request": { + "method": "GET", + "url": "http://localhost:8233/api/v1/namespaces/default/schedules?", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br" + }, + { + "name": "Accept-Language", + "value": "en-US" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Cookie", + "value": "_csrf=h5fpdnD9Rw3BWBkyO6Lf2eTrjPmhLMlR" + }, + { + "name": "Host", + "value": "localhost:8233" + }, + { + "name": "Origin", + "value": "http://localhost:3333" + }, + { + "name": "Referer", + "value": "http://localhost:3333/" + }, + { + "name": "Sec-Fetch-Dest", + "value": "empty" + }, + { + "name": "Sec-Fetch-Mode", + "value": "cors" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-site" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.35 Safari/537.36" + }, + { + "name": "X-CSRF-TOKEN", + "value": "h5fpdnD9Rw3BWBkyO6Lf2eTrjPmhLMlR" + } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "http://localhost:3333" + }, + { + "name": "Content-Length", + "value": "1333" + }, + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Date", + "value": "Wed, 07 Jun 2023 13:04:02 GMT" + }, + { + "name": "Grpc-Metadata-Content-Type", + "value": "application/grpc" + }, + { + "name": "Set-Cookie", + "value": "_csrf=h5fpdnD9Rw3BWBkyO6Lf2eTrjPmhLMlR; Path=/; Expires=Thu, 08 Jun 2023 13:04:02 GMT; Secure; SameSite=Strict" + }, + { + "name": "Vary", + "value": "Origin" + }, + { + "name": "Vary", + "value": "Cookie" + }, + { + "name": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "name": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "name": "X-Xss-Protection", + "value": "1; mode=block" + } + ], + "content": { + "size": -1, + "mimeType": "application/json", + "text": "{\n \"schedules\": [\n {\n \"scheduleId\": \"Scheduled Workflow\",\n \"memo\": null,\n \"searchAttributes\": {\n \"indexedFields\": {\n \"TemporalNamespaceDivision\": {\n \"metadata\": {\n \"encoding\": \"anNvbi9wbGFpbg==\",\n \"type\": \"S2V5d29yZA==\"\n },\n \"data\": \"IlRlbXBvcmFsU2NoZWR1bGVyIg==\"\n }\n }\n },\n \"info\": {\n \"spec\": {\n \"structuredCalendar\": [\n ],\n \"cronString\": [\n ],\n \"calendar\": [\n ],\n \"interval\": [\n {\n \"interval\": \"300s\",\n \"phase\": \"0s\"\n }\n ],\n \"excludeCalendar\": [\n ],\n \"excludeStructuredCalendar\": [\n ],\n \"startTime\": null,\n \"endTime\": null,\n \"jitter\": null,\n \"timezoneName\": \"\",\n \"timezoneData\": null\n },\n \"workflowType\": {\n \"name\": \"testWorkflow\"\n },\n \"notes\": \"\",\n \"paused\": false,\n \"recentActions\": [\n ],\n \"futureActionTimes\": [\n \"2023-06-07T13:05:00Z\",\n \"2023-06-07T13:10:00Z\",\n \"2023-06-07T13:15:00Z\",\n \"2023-06-07T13:20:00Z\",\n \"2023-06-07T13:25:00Z\"\n ]\n }\n }\n ],\n \"nextPageToken\": null\n}" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": 1.062 + } + } + ] + } +} \ No newline at end of file diff --git a/tests/accessibility/network-requests/with-workflows.har b/tests/accessibility/network-requests/with-workflows.har new file mode 100644 index 000000000..3a9fa28e4 --- /dev/null +++ b/tests/accessibility/network-requests/with-workflows.har @@ -0,0 +1,1256 @@ +{ + "log": { + "version": "1.2", + "creator": { + "name": "Playwright", + "version": "1.34.3" + }, + "browser": { + "name": "chromium", + "version": "114.0.5735.35" + }, + "entries": [ + { + "startedDateTime": "2023-06-07T13:11:00.291Z", + "time": 0.51, + "request": { + "method": "GET", + "url": "http://localhost:8233/api/v1/settings?", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br" + }, + { + "name": "Accept-Language", + "value": "en-US" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Host", + "value": "localhost:8233" + }, + { + "name": "Origin", + "value": "http://localhost:3333" + }, + { + "name": "Referer", + "value": "http://localhost:3333/" + }, + { + "name": "Sec-Fetch-Dest", + "value": "empty" + }, + { + "name": "Sec-Fetch-Mode", + "value": "cors" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-site" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.35 Safari/537.36" + } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "http://localhost:3333" + }, + { + "name": "Content-Length", + "value": "456" + }, + { + "name": "Content-Type", + "value": "application/json; charset=UTF-8" + }, + { + "name": "Date", + "value": "Wed, 07 Jun 2023 13:11:00 GMT" + }, + { + "name": "Set-Cookie", + "value": "_csrf=rJPOWqwMLbiKqVW6ORIhPavorpnXjacK; Path=/; Expires=Thu, 08 Jun 2023 13:11:00 GMT; Secure; SameSite=Strict" + }, + { + "name": "Vary", + "value": "Origin" + }, + { + "name": "Vary", + "value": "Cookie" + }, + { + "name": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "name": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "name": "X-Xss-Protection", + "value": "1; mode=block" + } + ], + "content": { + "size": -1, + "mimeType": "application/json; charset=UTF-8", + "text": "{\"Auth\":{\"Enabled\":false,\"Options\":null},\"DefaultNamespace\":\"\",\"ShowTemporalSystemNamespace\":false,\"FeedbackURL\":\"\",\"NotifyOnNewVersion\":false,\"Codec\":{\"Endpoint\":\"\",\"PassAccessToken\":false,\"IncludeCredentials\":false,\"DecodeEventHistoryDownload\":false},\"Version\":\"2.15.0\",\"DisableWriteActions\":false,\"WorkflowTerminateDisabled\":false,\"WorkflowCancelDisabled\":false,\"WorkflowSignalDisabled\":false,\"WorkflowResetDisabled\":false,\"BatchActionsDisabled\":false}\n" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": 0.51 + } + }, + { + "startedDateTime": "2023-06-07T13:11:00.313Z", + "time": 9.146, + "request": { + "method": "GET", + "url": "http://localhost:8233/api/v1/namespaces?", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br" + }, + { + "name": "Accept-Language", + "value": "en-US" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Cookie", + "value": "_csrf=rJPOWqwMLbiKqVW6ORIhPavorpnXjacK" + }, + { + "name": "Host", + "value": "localhost:8233" + }, + { + "name": "Origin", + "value": "http://localhost:3333" + }, + { + "name": "Referer", + "value": "http://localhost:3333/" + }, + { + "name": "Sec-Fetch-Dest", + "value": "empty" + }, + { + "name": "Sec-Fetch-Mode", + "value": "cors" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-site" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.35 Safari/537.36" + }, + { + "name": "X-CSRF-TOKEN", + "value": "rJPOWqwMLbiKqVW6ORIhPavorpnXjacK" + } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "http://localhost:3333" + }, + { + "name": "Content-Length", + "value": "1995" + }, + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Date", + "value": "Wed, 07 Jun 2023 13:11:00 GMT" + }, + { + "name": "Grpc-Metadata-Content-Type", + "value": "application/grpc" + }, + { + "name": "Set-Cookie", + "value": "_csrf=rJPOWqwMLbiKqVW6ORIhPavorpnXjacK; Path=/; Expires=Thu, 08 Jun 2023 13:11:00 GMT; Secure; SameSite=Strict" + }, + { + "name": "Vary", + "value": "Origin" + }, + { + "name": "Vary", + "value": "Cookie" + }, + { + "name": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "name": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "name": "X-Xss-Protection", + "value": "1; mode=block" + } + ], + "content": { + "size": -1, + "mimeType": "application/json", + "text": "{\n \"namespaces\": [\n {\n \"namespaceInfo\": {\n \"name\": \"default\",\n \"state\": \"Registered\",\n \"description\": \"\",\n \"ownerEmail\": \"\",\n \"data\": {\n },\n \"id\": \"1bc6130b-9a57-4ec4-b4ba-79f7d321f84f\",\n \"supportsSchedules\": true\n },\n \"config\": {\n \"workflowExecutionRetentionTtl\": \"86400s\",\n \"badBinaries\": {\n \"binaries\": {\n }\n },\n \"historyArchivalState\": \"Disabled\",\n \"historyArchivalUri\": \"\",\n \"visibilityArchivalState\": \"Disabled\",\n \"visibilityArchivalUri\": \"\",\n \"customSearchAttributeAliases\": {\n }\n },\n \"replicationConfig\": {\n \"activeClusterName\": \"active\",\n \"clusters\": [\n {\n \"clusterName\": \"active\"\n }\n ],\n \"state\": \"Unspecified\"\n },\n \"failoverVersion\": \"0\",\n \"isGlobalNamespace\": false,\n \"failoverHistory\": [\n ]\n },\n {\n \"namespaceInfo\": {\n \"name\": \"temporal-system\",\n \"state\": \"Registered\",\n \"description\": \"Temporal internal system namespace\",\n \"ownerEmail\": \"temporal-core@temporal.io\",\n \"data\": {\n },\n \"id\": \"32049b68-7872-4094-8e63-d0dd59896a83\",\n \"supportsSchedules\": true\n },\n \"config\": {\n \"workflowExecutionRetentionTtl\": \"604800s\",\n \"badBinaries\": {\n \"binaries\": {\n }\n },\n \"historyArchivalState\": \"Disabled\",\n \"historyArchivalUri\": \"\",\n \"visibilityArchivalState\": \"Disabled\",\n \"visibilityArchivalUri\": \"\",\n \"customSearchAttributeAliases\": {\n }\n },\n \"replicationConfig\": {\n \"activeClusterName\": \"active\",\n \"clusters\": [\n {\n \"clusterName\": \"active\"\n }\n ],\n \"state\": \"Unspecified\"\n },\n \"failoverVersion\": \"0\",\n \"isGlobalNamespace\": false,\n \"failoverHistory\": [\n ]\n }\n ],\n \"nextPageToken\": null\n}" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": 9.146 + } + }, + { + "startedDateTime": "2023-06-07T13:11:00.313Z", + "time": 10.942, + "request": { + "method": "GET", + "url": "http://localhost:8233/api/v1/search-attributes?", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br" + }, + { + "name": "Accept-Language", + "value": "en-US" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Cookie", + "value": "_csrf=rJPOWqwMLbiKqVW6ORIhPavorpnXjacK" + }, + { + "name": "Host", + "value": "localhost:8233" + }, + { + "name": "Origin", + "value": "http://localhost:3333" + }, + { + "name": "Referer", + "value": "http://localhost:3333/" + }, + { + "name": "Sec-Fetch-Dest", + "value": "empty" + }, + { + "name": "Sec-Fetch-Mode", + "value": "cors" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-site" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.35 Safari/537.36" + }, + { + "name": "X-CSRF-TOKEN", + "value": "rJPOWqwMLbiKqVW6ORIhPavorpnXjacK" + } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "http://localhost:3333" + }, + { + "name": "Content-Length", + "value": "1444" + }, + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Date", + "value": "Wed, 07 Jun 2023 13:11:00 GMT" + }, + { + "name": "Grpc-Metadata-Content-Type", + "value": "application/grpc" + }, + { + "name": "Set-Cookie", + "value": "_csrf=rJPOWqwMLbiKqVW6ORIhPavorpnXjacK; Path=/; Expires=Thu, 08 Jun 2023 13:11:00 GMT; Secure; SameSite=Strict" + }, + { + "name": "Vary", + "value": "Origin" + }, + { + "name": "Vary", + "value": "Cookie" + }, + { + "name": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "name": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "name": "X-Xss-Protection", + "value": "1; mode=block" + } + ], + "content": { + "size": -1, + "mimeType": "application/json", + "text": "{\n \"keys\": {\n \"BatcherNamespace\": \"Keyword\",\n \"BatcherUser\": \"Keyword\",\n \"BinaryChecksums\": \"KeywordList\",\n \"Bool01\": \"Bool\",\n \"Bool02\": \"Bool\",\n \"Bool03\": \"Bool\",\n \"CloseTime\": \"Datetime\",\n \"Datetime01\": \"Datetime\",\n \"Datetime02\": \"Datetime\",\n \"Datetime03\": \"Datetime\",\n \"Double01\": \"Double\",\n \"Double02\": \"Double\",\n \"Double03\": \"Double\",\n \"ExecutionDuration\": \"Int\",\n \"ExecutionStatus\": \"Keyword\",\n \"ExecutionTime\": \"Datetime\",\n \"HistoryLength\": \"Int\",\n \"HistorySizeBytes\": \"Int\",\n \"Int01\": \"Int\",\n \"Int02\": \"Int\",\n \"Int03\": \"Int\",\n \"Keyword01\": \"Keyword\",\n \"Keyword02\": \"Keyword\",\n \"Keyword03\": \"Keyword\",\n \"Keyword04\": \"Keyword\",\n \"Keyword05\": \"Keyword\",\n \"Keyword06\": \"Keyword\",\n \"Keyword07\": \"Keyword\",\n \"Keyword08\": \"Keyword\",\n \"Keyword09\": \"Keyword\",\n \"Keyword10\": \"Keyword\",\n \"KeywordList01\": \"KeywordList\",\n \"KeywordList02\": \"KeywordList\",\n \"KeywordList03\": \"KeywordList\",\n \"RunId\": \"Keyword\",\n \"StartTime\": \"Datetime\",\n \"StateTransitionCount\": \"Int\",\n \"TaskQueue\": \"Keyword\",\n \"TemporalChangeVersion\": \"KeywordList\",\n \"TemporalNamespaceDivision\": \"Keyword\",\n \"TemporalSchedulePaused\": \"Bool\",\n \"TemporalScheduledById\": \"Keyword\",\n \"TemporalScheduledStartTime\": \"Datetime\",\n \"Text01\": \"Text\",\n \"Text02\": \"Text\",\n \"Text03\": \"Text\",\n \"WorkflowId\": \"Keyword\",\n \"WorkflowType\": \"Keyword\"\n }\n}" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": 10.942 + } + }, + { + "startedDateTime": "2023-06-07T13:11:00.313Z", + "time": 12.483, + "request": { + "method": "GET", + "url": "http://localhost:8233/api/v1/cluster-info?", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br" + }, + { + "name": "Accept-Language", + "value": "en-US" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Cookie", + "value": "_csrf=rJPOWqwMLbiKqVW6ORIhPavorpnXjacK" + }, + { + "name": "Host", + "value": "localhost:8233" + }, + { + "name": "Origin", + "value": "http://localhost:3333" + }, + { + "name": "Referer", + "value": "http://localhost:3333/" + }, + { + "name": "Sec-Fetch-Dest", + "value": "empty" + }, + { + "name": "Sec-Fetch-Mode", + "value": "cors" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-site" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.35 Safari/537.36" + }, + { + "name": "X-CSRF-TOKEN", + "value": "rJPOWqwMLbiKqVW6ORIhPavorpnXjacK" + } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "http://localhost:3333" + }, + { + "name": "Content-Length", + "value": "936" + }, + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Date", + "value": "Wed, 07 Jun 2023 13:11:00 GMT" + }, + { + "name": "Grpc-Metadata-Content-Type", + "value": "application/grpc" + }, + { + "name": "Set-Cookie", + "value": "_csrf=rJPOWqwMLbiKqVW6ORIhPavorpnXjacK; Path=/; Expires=Thu, 08 Jun 2023 13:11:00 GMT; Secure; SameSite=Strict" + }, + { + "name": "Vary", + "value": "Origin" + }, + { + "name": "Vary", + "value": "Cookie" + }, + { + "name": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "name": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "name": "X-Xss-Protection", + "value": "1; mode=block" + } + ], + "content": { + "size": -1, + "mimeType": "application/json", + "text": "{\n \"supportedClients\": {\n \"temporal-cli\": \"\\u003c2.0.0\",\n \"temporal-go\": \"\\u003c2.0.0\",\n \"temporal-java\": \"\\u003c2.0.0\",\n \"temporal-php\": \"\\u003c2.0.0\",\n \"temporal-server\": \"\\u003c2.0.0\",\n \"temporal-typescript\": \"\\u003c2.0.0\",\n \"temporal-ui\": \"\\u003c3.0.0\"\n },\n \"serverVersion\": \"1.20.2\",\n \"clusterId\": \"f9c299e0-a75f-4306-bfd6-17022bdb56b5\",\n \"versionInfo\": {\n \"current\": {\n \"version\": \"1.20.2\",\n \"releaseTime\": \"2023-04-20T18:15:00Z\",\n \"notes\": \"\"\n },\n \"recommended\": {\n \"version\": \"1.20.2\",\n \"releaseTime\": \"2023-04-20T18:15:00Z\",\n \"notes\": \"\"\n },\n \"instructions\": \"\",\n \"alerts\": [\n {\n \"message\": \"🪐 A new release is available!\",\n \"severity\": \"Low\"\n }\n ],\n \"lastUpdateTime\": \"2023-06-07T13:05:26.648012Z\"\n },\n \"clusterName\": \"active\",\n \"historyShardCount\": 1,\n \"persistenceStore\": \"sqlite\",\n \"visibilityStore\": \"sqlite\"\n}" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": 12.483 + } + }, + { + "startedDateTime": "2023-06-07T13:11:00.384Z", + "time": 1.324, + "request": { + "method": "GET", + "url": "http://localhost:8233/api/v1/namespaces/default/workflows/Running-PGlmzINUdHKb_MRK9uhf5/runs/bab2d175-4dc5-476e-b6c7-aa3d98ae73d5?", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br" + }, + { + "name": "Accept-Language", + "value": "en-US" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Cookie", + "value": "_csrf=rJPOWqwMLbiKqVW6ORIhPavorpnXjacK" + }, + { + "name": "Host", + "value": "localhost:8233" + }, + { + "name": "Origin", + "value": "http://localhost:3333" + }, + { + "name": "Referer", + "value": "http://localhost:3333/" + }, + { + "name": "Sec-Fetch-Dest", + "value": "empty" + }, + { + "name": "Sec-Fetch-Mode", + "value": "cors" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-site" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.35 Safari/537.36" + }, + { + "name": "X-CSRF-TOKEN", + "value": "rJPOWqwMLbiKqVW6ORIhPavorpnXjacK" + } + ], + "queryString": [], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "http://localhost:3333" + }, + { + "name": "Content-Length", + "value": "1698" + }, + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Date", + "value": "Wed, 07 Jun 2023 13:11:00 GMT" + }, + { + "name": "Grpc-Metadata-Content-Type", + "value": "application/grpc" + }, + { + "name": "Set-Cookie", + "value": "_csrf=rJPOWqwMLbiKqVW6ORIhPavorpnXjacK; Path=/; Expires=Thu, 08 Jun 2023 13:11:00 GMT; Secure; SameSite=Strict" + }, + { + "name": "Vary", + "value": "Origin" + }, + { + "name": "Vary", + "value": "Cookie" + }, + { + "name": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "name": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "name": "X-Xss-Protection", + "value": "1; mode=block" + } + ], + "content": { + "size": -1, + "mimeType": "application/json", + "text": "{\n \"executionConfig\": {\n \"taskQueue\": {\n \"name\": \"workflow-statuses\",\n \"kind\": \"Normal\"\n },\n \"workflowExecutionTimeout\": null,\n \"workflowRunTimeout\": null,\n \"defaultWorkflowTaskTimeout\": \"10s\"\n },\n \"workflowExecutionInfo\": {\n \"execution\": {\n \"workflowId\": \"Running-PGlmzINUdHKb_MRK9uhf5\",\n \"runId\": \"bab2d175-4dc5-476e-b6c7-aa3d98ae73d5\"\n },\n \"type\": {\n \"name\": \"longRunning\"\n },\n \"startTime\": \"2023-06-07T13:05:46.554314Z\",\n \"closeTime\": null,\n \"status\": \"Running\",\n \"historyLength\": \"5\",\n \"parentNamespaceId\": \"\",\n \"parentExecution\": null,\n \"executionTime\": \"2023-06-07T13:05:46.554314Z\",\n \"memo\": {\n \"fields\": {\n }\n },\n \"searchAttributes\": {\n \"indexedFields\": {\n \"BinaryChecksums\": {\n \"metadata\": {\n \"encoding\": \"anNvbi9wbGFpbg==\",\n \"type\": \"S2V5d29yZExpc3Q=\"\n },\n \"data\": \"WyJAdGVtcG9yYWxpby93b3JrZXJAMS42LjArMDc5NjY5NDFlNzRlNGYwNDJiYzYyODdhZmVlOWQ2Y2NhMjczMjUxNDhmYTA4ODJiNjJkMjdlM2I5Mjc3ODY2MCJd\"\n }\n }\n },\n \"autoResetPoints\": {\n \"points\": [\n {\n \"binaryChecksum\": \"@temporalio/worker@1.6.0+07966941e74e4f042bc6287afee9d6cca27325148fa0882b62d27e3b92778660\",\n \"runId\": \"bab2d175-4dc5-476e-b6c7-aa3d98ae73d5\",\n \"firstWorkflowTaskCompletedId\": \"4\",\n \"createTime\": \"2023-06-07T13:05:46.566624Z\",\n \"expireTime\": null,\n \"resettable\": true\n }\n ]\n },\n \"taskQueue\": \"workflow-statuses\",\n \"stateTransitionCount\": \"3\",\n \"historySizeBytes\": \"545\"\n },\n \"pendingActivities\": [\n ],\n \"pendingChildren\": [\n ],\n \"pendingWorkflowTask\": null\n}" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": 1.324 + } + }, + { + "startedDateTime": "2023-06-07T13:11:00.431Z", + "time": 1.054, + "request": { + "method": "GET", + "url": "http://localhost:8233/api/v1/namespaces/default/task-queues/workflow-statuses?taskQueueType=1", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br" + }, + { + "name": "Accept-Language", + "value": "en-US" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Cookie", + "value": "_csrf=rJPOWqwMLbiKqVW6ORIhPavorpnXjacK" + }, + { + "name": "Host", + "value": "localhost:8233" + }, + { + "name": "Origin", + "value": "http://localhost:3333" + }, + { + "name": "Referer", + "value": "http://localhost:3333/" + }, + { + "name": "Sec-Fetch-Dest", + "value": "empty" + }, + { + "name": "Sec-Fetch-Mode", + "value": "cors" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-site" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.35 Safari/537.36" + }, + { + "name": "X-CSRF-TOKEN", + "value": "rJPOWqwMLbiKqVW6ORIhPavorpnXjacK" + } + ], + "queryString": [ + { + "name": "taskQueueType", + "value": "1" + } + ], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "http://localhost:3333" + }, + { + "name": "Content-Length", + "value": "234" + }, + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Date", + "value": "Wed, 07 Jun 2023 13:11:00 GMT" + }, + { + "name": "Grpc-Metadata-Content-Type", + "value": "application/grpc" + }, + { + "name": "Set-Cookie", + "value": "_csrf=rJPOWqwMLbiKqVW6ORIhPavorpnXjacK; Path=/; Expires=Thu, 08 Jun 2023 13:11:00 GMT; Secure; SameSite=Strict" + }, + { + "name": "Vary", + "value": "Origin" + }, + { + "name": "Vary", + "value": "Cookie" + }, + { + "name": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "name": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "name": "X-Xss-Protection", + "value": "1; mode=block" + } + ], + "content": { + "size": -1, + "mimeType": "application/json", + "text": "{\n \"pollers\": [\n {\n \"lastAccessTime\": \"2023-06-07T13:10:43.655834Z\",\n \"identity\": \"49916@temporal-macbook-pro.local\",\n \"ratePerSecond\": 100000,\n \"workerVersioningId\": null\n }\n ],\n \"taskQueueStatus\": null\n}" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": 1.054 + } + }, + { + "startedDateTime": "2023-06-07T13:11:00.432Z", + "time": 1.311, + "request": { + "method": "GET", + "url": "http://localhost:8233/api/v1/namespaces/default/task-queues/workflow-statuses?taskQueueType=2", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br" + }, + { + "name": "Accept-Language", + "value": "en-US" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Cookie", + "value": "_csrf=rJPOWqwMLbiKqVW6ORIhPavorpnXjacK" + }, + { + "name": "Host", + "value": "localhost:8233" + }, + { + "name": "Origin", + "value": "http://localhost:3333" + }, + { + "name": "Referer", + "value": "http://localhost:3333/" + }, + { + "name": "Sec-Fetch-Dest", + "value": "empty" + }, + { + "name": "Sec-Fetch-Mode", + "value": "cors" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-site" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.35 Safari/537.36" + }, + { + "name": "X-CSRF-TOKEN", + "value": "rJPOWqwMLbiKqVW6ORIhPavorpnXjacK" + } + ], + "queryString": [ + { + "name": "taskQueueType", + "value": "2" + } + ], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "http://localhost:3333" + }, + { + "name": "Content-Length", + "value": "234" + }, + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Date", + "value": "Wed, 07 Jun 2023 13:11:00 GMT" + }, + { + "name": "Grpc-Metadata-Content-Type", + "value": "application/grpc" + }, + { + "name": "Set-Cookie", + "value": "_csrf=rJPOWqwMLbiKqVW6ORIhPavorpnXjacK; Path=/; Expires=Thu, 08 Jun 2023 13:11:00 GMT; Secure; SameSite=Strict" + }, + { + "name": "Vary", + "value": "Origin" + }, + { + "name": "Vary", + "value": "Cookie" + }, + { + "name": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "name": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "name": "X-Xss-Protection", + "value": "1; mode=block" + } + ], + "content": { + "size": -1, + "mimeType": "application/json", + "text": "{\n \"pollers\": [\n {\n \"lastAccessTime\": \"2023-06-07T13:10:44.666105Z\",\n \"identity\": \"49916@temporal-macbook-pro.local\",\n \"ratePerSecond\": 100000,\n \"workerVersioningId\": null\n }\n ],\n \"taskQueueStatus\": null\n}" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": 1.311 + } + }, + { + "startedDateTime": "2023-06-07T13:11:00.492Z", + "time": 1.124, + "request": { + "method": "GET", + "url": "http://localhost:8233/api/v1/namespaces/default/workflows/Running-PGlmzINUdHKb_MRK9uhf5/history?maximumPageSize=20&runId=bab2d175-4dc5-476e-b6c7-aa3d98ae73d5", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br" + }, + { + "name": "Accept-Language", + "value": "en-US" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Cookie", + "value": "_csrf=rJPOWqwMLbiKqVW6ORIhPavorpnXjacK" + }, + { + "name": "Host", + "value": "localhost:8233" + }, + { + "name": "Origin", + "value": "http://localhost:3333" + }, + { + "name": "Referer", + "value": "http://localhost:3333/" + }, + { + "name": "Sec-Fetch-Dest", + "value": "empty" + }, + { + "name": "Sec-Fetch-Mode", + "value": "cors" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-site" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.35 Safari/537.36" + }, + { + "name": "X-CSRF-TOKEN", + "value": "rJPOWqwMLbiKqVW6ORIhPavorpnXjacK" + } + ], + "queryString": [ + { + "name": "maximumPageSize", + "value": "20" + } + ], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "http://localhost:3333" + }, + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Date", + "value": "Wed, 07 Jun 2023 13:11:00 GMT" + }, + { + "name": "Grpc-Metadata-Content-Type", + "value": "application/grpc" + }, + { + "name": "Set-Cookie", + "value": "_csrf=rJPOWqwMLbiKqVW6ORIhPavorpnXjacK; Path=/; Expires=Thu, 08 Jun 2023 13:11:00 GMT; Secure; SameSite=Strict" + }, + { + "name": "Transfer-Encoding", + "value": "chunked" + }, + { + "name": "Vary", + "value": "Origin" + }, + { + "name": "Vary", + "value": "Cookie" + }, + { + "name": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "name": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "name": "X-Xss-Protection", + "value": "1; mode=block" + } + ], + "content": { + "size": -1, + "mimeType": "application/json", + "text": "{\n \"history\": {\n \"events\": [\n {\n \"eventId\": \"1\",\n \"eventTime\": \"2023-06-07T13:05:46.554314Z\",\n \"eventType\": \"WorkflowExecutionStarted\",\n \"version\": \"0\",\n \"taskId\": \"1048770\",\n \"workerMayIgnore\": false,\n \"workflowExecutionStartedEventAttributes\": {\n \"workflowType\": {\n \"name\": \"longRunning\"\n },\n \"parentWorkflowNamespace\": \"\",\n \"parentWorkflowNamespaceId\": \"\",\n \"parentWorkflowExecution\": null,\n \"parentInitiatedEventId\": \"0\",\n \"taskQueue\": {\n \"name\": \"workflow-statuses\",\n \"kind\": \"Normal\"\n },\n \"input\": {\n \"payloads\": [\n ]\n },\n \"workflowExecutionTimeout\": null,\n \"workflowRunTimeout\": null,\n \"workflowTaskTimeout\": \"10s\",\n \"continuedExecutionRunId\": \"\",\n \"initiator\": \"Unspecified\",\n \"continuedFailure\": null,\n \"lastCompletionResult\": null,\n \"originalExecutionRunId\": \"bab2d175-4dc5-476e-b6c7-aa3d98ae73d5\",\n \"identity\": \"49937@temporal-macbook-pro.local\",\n \"firstExecutionRunId\": \"bab2d175-4dc5-476e-b6c7-aa3d98ae73d5\",\n \"retryPolicy\": null,\n \"attempt\": 1,\n \"workflowExecutionExpirationTime\": null,\n \"cronSchedule\": \"\",\n \"firstWorkflowTaskBackoff\": \"0s\",\n \"memo\": null,\n \"searchAttributes\": null,\n \"prevAutoResetPoints\": null,\n \"header\": {\n \"fields\": {\n }\n },\n \"parentInitiatedEventVersion\": \"0\"\n }\n },\n {\n \"eventId\": \"2\",\n \"eventTime\": \"2023-06-07T13:05:46.554356Z\",\n \"eventType\": \"WorkflowTaskScheduled\",\n \"version\": \"0\",\n \"taskId\": \"1048771\",\n \"workerMayIgnore\": false,\n \"workflowTaskScheduledEventAttributes\": {\n \"taskQueue\": {\n \"name\": \"workflow-statuses\",\n \"kind\": \"Normal\"\n },\n \"startToCloseTimeout\": \"10s\",\n \"attempt\": 1\n }\n },\n {\n \"eventId\": \"3\",\n \"eventTime\": \"2023-06-07T13:05:46.556113Z\",\n \"eventType\": \"WorkflowTaskStarted\",\n \"version\": \"0\",\n \"taskId\": \"1048776\",\n \"workerMayIgnore\": false,\n \"workflowTaskStartedEventAttributes\": {\n \"scheduledEventId\": \"2\",\n \"identity\": \"49916@temporal-macbook-pro.local\",\n \"requestId\": \"3acda9d7-a09c-4da1-b9bf-92658f0ed101\",\n \"suggestContinueAsNew\": false,\n \"historySizeBytes\": \"247\"\n }\n },\n {\n \"eventId\": \"4\",\n \"eventTime\": \"2023-06-07T13:05:46.566623Z\",\n \"eventType\": \"WorkflowTaskCompleted\",\n \"version\": \"0\",\n \"taskId\": \"1048804\",\n \"workerMayIgnore\": false,\n \"workflowTaskCompletedEventAttributes\": {\n \"scheduledEventId\": \"2\",\n \"startedEventId\": \"3\",\n \"identity\": \"49916@temporal-macbook-pro.local\",\n \"binaryChecksum\": \"@temporalio/worker@1.6.0+07966941e74e4f042bc6287afee9d6cca27325148fa0882b62d27e3b92778660\",\n \"workerVersioningId\": null,\n \"sdkMetadata\": null,\n \"meteringMetadata\": null\n }\n },\n {\n \"eventId\": \"5\",\n \"eventTime\": \"2023-06-07T13:05:46.566636Z\",\n \"eventType\": \"TimerStarted\",\n \"version\": \"0\",\n \"taskId\": \"1048805\",\n \"workerMayIgnore\": false,\n \"timerStartedEventAttributes\": {\n \"timerId\": \"1\",\n \"startToFireTimeout\": \"864000s\",\n \"workflowTaskCompletedEventId\": \"4\"\n }\n }\n ]\n },\n \"rawHistory\": [\n ],\n \"nextPageToken\": null,\n \"archived\": false\n}" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": 1.124 + } + }, + { + "startedDateTime": "2023-06-07T13:11:00.689Z", + "time": 1.794, + "request": { + "method": "GET", + "url": "http://localhost:8233/api/v1/namespaces/default/workflows/Running-PGlmzINUdHKb_MRK9uhf5/history-reverse?maximumPageSize=20&runId=bab2d175-4dc5-476e-b6c7-aa3d98ae73d5", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "*/*" + }, + { + "name": "Accept-Encoding", + "value": "gzip, deflate, br" + }, + { + "name": "Accept-Language", + "value": "en-US" + }, + { + "name": "Connection", + "value": "keep-alive" + }, + { + "name": "Cookie", + "value": "_csrf=rJPOWqwMLbiKqVW6ORIhPavorpnXjacK" + }, + { + "name": "Host", + "value": "localhost:8233" + }, + { + "name": "Origin", + "value": "http://localhost:3333" + }, + { + "name": "Referer", + "value": "http://localhost:3333/" + }, + { + "name": "Sec-Fetch-Dest", + "value": "empty" + }, + { + "name": "Sec-Fetch-Mode", + "value": "cors" + }, + { + "name": "Sec-Fetch-Site", + "value": "same-site" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.35 Safari/537.36" + }, + { + "name": "X-CSRF-TOKEN", + "value": "rJPOWqwMLbiKqVW6ORIhPavorpnXjacK" + } + ], + "queryString": [ + { + "name": "maximumPageSize", + "value": "20" + } + ], + "headersSize": -1, + "bodySize": -1 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Access-Control-Allow-Credentials", + "value": "true" + }, + { + "name": "Access-Control-Allow-Origin", + "value": "http://localhost:3333" + }, + { + "name": "Content-Type", + "value": "application/json" + }, + { + "name": "Date", + "value": "Wed, 07 Jun 2023 13:11:00 GMT" + }, + { + "name": "Grpc-Metadata-Content-Type", + "value": "application/grpc" + }, + { + "name": "Set-Cookie", + "value": "_csrf=rJPOWqwMLbiKqVW6ORIhPavorpnXjacK; Path=/; Expires=Thu, 08 Jun 2023 13:11:00 GMT; Secure; SameSite=Strict" + }, + { + "name": "Transfer-Encoding", + "value": "chunked" + }, + { + "name": "Vary", + "value": "Origin" + }, + { + "name": "Vary", + "value": "Cookie" + }, + { + "name": "X-Content-Type-Options", + "value": "nosniff" + }, + { + "name": "X-Frame-Options", + "value": "SAMEORIGIN" + }, + { + "name": "X-Xss-Protection", + "value": "1; mode=block" + } + ], + "content": { + "size": -1, + "mimeType": "application/json", + "text": "{\n \"history\": {\n \"events\": [\n {\n \"eventId\": \"5\",\n \"eventTime\": \"2023-06-07T13:05:46.566636Z\",\n \"eventType\": \"TimerStarted\",\n \"version\": \"0\",\n \"taskId\": \"1048805\",\n \"workerMayIgnore\": false,\n \"timerStartedEventAttributes\": {\n \"timerId\": \"1\",\n \"startToFireTimeout\": \"864000s\",\n \"workflowTaskCompletedEventId\": \"4\"\n }\n },\n {\n \"eventId\": \"4\",\n \"eventTime\": \"2023-06-07T13:05:46.566623Z\",\n \"eventType\": \"WorkflowTaskCompleted\",\n \"version\": \"0\",\n \"taskId\": \"1048804\",\n \"workerMayIgnore\": false,\n \"workflowTaskCompletedEventAttributes\": {\n \"scheduledEventId\": \"2\",\n \"startedEventId\": \"3\",\n \"identity\": \"49916@temporal-macbook-pro.local\",\n \"binaryChecksum\": \"@temporalio/worker@1.6.0+07966941e74e4f042bc6287afee9d6cca27325148fa0882b62d27e3b92778660\",\n \"workerVersioningId\": null,\n \"sdkMetadata\": null,\n \"meteringMetadata\": null\n }\n },\n {\n \"eventId\": \"3\",\n \"eventTime\": \"2023-06-07T13:05:46.556113Z\",\n \"eventType\": \"WorkflowTaskStarted\",\n \"version\": \"0\",\n \"taskId\": \"1048776\",\n \"workerMayIgnore\": false,\n \"workflowTaskStartedEventAttributes\": {\n \"scheduledEventId\": \"2\",\n \"identity\": \"49916@temporal-macbook-pro.local\",\n \"requestId\": \"3acda9d7-a09c-4da1-b9bf-92658f0ed101\",\n \"suggestContinueAsNew\": false,\n \"historySizeBytes\": \"247\"\n }\n },\n {\n \"eventId\": \"2\",\n \"eventTime\": \"2023-06-07T13:05:46.554356Z\",\n \"eventType\": \"WorkflowTaskScheduled\",\n \"version\": \"0\",\n \"taskId\": \"1048771\",\n \"workerMayIgnore\": false,\n \"workflowTaskScheduledEventAttributes\": {\n \"taskQueue\": {\n \"name\": \"workflow-statuses\",\n \"kind\": \"Normal\"\n },\n \"startToCloseTimeout\": \"10s\",\n \"attempt\": 1\n }\n },\n {\n \"eventId\": \"1\",\n \"eventTime\": \"2023-06-07T13:05:46.554314Z\",\n \"eventType\": \"WorkflowExecutionStarted\",\n \"version\": \"0\",\n \"taskId\": \"1048770\",\n \"workerMayIgnore\": false,\n \"workflowExecutionStartedEventAttributes\": {\n \"workflowType\": {\n \"name\": \"longRunning\"\n },\n \"parentWorkflowNamespace\": \"\",\n \"parentWorkflowNamespaceId\": \"\",\n \"parentWorkflowExecution\": null,\n \"parentInitiatedEventId\": \"0\",\n \"taskQueue\": {\n \"name\": \"workflow-statuses\",\n \"kind\": \"Normal\"\n },\n \"input\": {\n \"payloads\": [\n ]\n },\n \"workflowExecutionTimeout\": null,\n \"workflowRunTimeout\": null,\n \"workflowTaskTimeout\": \"10s\",\n \"continuedExecutionRunId\": \"\",\n \"initiator\": \"Unspecified\",\n \"continuedFailure\": null,\n \"lastCompletionResult\": null,\n \"originalExecutionRunId\": \"bab2d175-4dc5-476e-b6c7-aa3d98ae73d5\",\n \"identity\": \"49937@temporal-macbook-pro.local\",\n \"firstExecutionRunId\": \"bab2d175-4dc5-476e-b6c7-aa3d98ae73d5\",\n \"retryPolicy\": null,\n \"attempt\": 1,\n \"workflowExecutionExpirationTime\": null,\n \"cronSchedule\": \"\",\n \"firstWorkflowTaskBackoff\": \"0s\",\n \"memo\": null,\n \"searchAttributes\": null,\n \"prevAutoResetPoints\": null,\n \"header\": {\n \"fields\": {\n }\n },\n \"parentInitiatedEventVersion\": \"0\"\n }\n }\n ]\n },\n \"nextPageToken\": null\n}" + }, + "headersSize": -1, + "bodySize": -1, + "redirectURL": "" + }, + "cache": {}, + "timings": { + "send": -1, + "wait": -1, + "receive": 1.794 + } + } + ] + } +} \ No newline at end of file diff --git a/tests/accessibility/with-schedules.accessibility.spec.ts b/tests/accessibility/with-schedules.accessibility.spec.ts new file mode 100644 index 000000000..1606b0949 --- /dev/null +++ b/tests/accessibility/with-schedules.accessibility.spec.ts @@ -0,0 +1,45 @@ +import AxeBuilder from '@axe-core/playwright'; +import { expect, test } from '@playwright/test'; + +import { attachViolations } from '~/test-utilities/attach-violations'; + +test.beforeEach(async ({ page }) => { + await page.routeFromHAR( + './tests/accessibility/network-requests/with-schedules.har', + { + updateMode: 'minimal', + update: false, + updateContent: 'embed', + notFound: 'fallback', + url: '**/api/v1/**', + }, + ); +}); + +const pages = [ + { title: 'Schedules', url: '/namespaces/default/schedules' }, + { + title: 'View Schedule', + url: '/namespaces/default/schedules/Scheduled%20Workflow', + }, +]; + +test.describe('Accessibility: With Schedules', () => { + for (const { title, url } of pages) { + test(`${title} page (${url}) should not have any automatically detectable accessibility issues`, async ({ + page, + }, testInfo) => { + await page.goto(url); + await page.waitForRequest('**/api/v1/**'); + await page.waitForSelector('#content', { state: 'visible' }); + + const accessibilityScanResults = await new AxeBuilder({ + page, + }).analyze(); + + await attachViolations(testInfo, accessibilityScanResults, page); + + expect(accessibilityScanResults.violations).toEqual([]); + }); + } +}); diff --git a/tests/accessibility/with-workflows.accessibility.spec.ts b/tests/accessibility/with-workflows.accessibility.spec.ts new file mode 100644 index 000000000..48c6a520b --- /dev/null +++ b/tests/accessibility/with-workflows.accessibility.spec.ts @@ -0,0 +1,64 @@ +import AxeBuilder from '@axe-core/playwright'; +import { expect, test } from '@playwright/test'; + +import { attachViolations } from '~/test-utilities/attach-violations'; + +test.beforeEach(async ({ page }) => { + await page.routeFromHAR( + './tests/accessibility/network-requests/with-workflows.har', + { + updateMode: 'minimal', + update: false, + updateContent: 'embed', + notFound: 'fallback', + url: '**/api/v1/**', + }, + ); +}); + +const workflowId = + '/namespaces/default/workflows/Running-PGlmzINUdHKb_MRK9uhf5/bab2d175-4dc5-476e-b6c7-aa3d98ae73d5'; + +const pages = [ + { title: 'Workflow List', url: '/namespaces/default/workflows' }, + { + title: 'Workflow Details', + url: workflowId + '/history', + }, + { + title: 'Pending Activities', + url: workflowId + '/pending-activities', + }, + { + title: 'Call Stack', + url: workflowId + '/call-stack', + }, + { + title: 'Query', + url: workflowId + '/query', + }, + { + title: 'Workers', + url: workflowId + '/workers', + }, +]; + +test.describe('Accessibility: With Workflows', () => { + for (const { title, url } of pages) { + test(`${title} page (${url}) should not have any automatically detectable accessibility issues`, async ({ + page, + }, testInfo) => { + await page.goto(url); + await page.waitForRequest('**/api/v1/**'); + await page.waitForSelector('#content', { state: 'visible' }); + + const accessibilityScanResults = await new AxeBuilder({ + page, + }).analyze(); + + await attachViolations(testInfo, accessibilityScanResults, page); + + expect(accessibilityScanResults.violations).toEqual([]); + }); + } +}); diff --git a/tests/e2e/call-stack.spec.ts b/tests/e2e/call-stack.spec.ts new file mode 100644 index 000000000..9b4d9ac5e --- /dev/null +++ b/tests/e2e/call-stack.spec.ts @@ -0,0 +1,49 @@ +import { expect, test } from '@playwright/test'; + +import mockQueryApiWithStackTraceError from '~/test-utilities/mocks/query'; + +test.beforeEach(async ({ page, baseURL }) => { + await page.goto(`${baseURL}/namespaces/default/workflows`); +}); + +// eslint-disable-next-line playwright/no-skipped-test +test.skip('Call Stack With Completed Workflow', () => { + test('should show No Call Stack for completed workflow', async ({ page }) => { + await page + .getByText('completed-workflow') + .click({ position: { x: 0, y: 0 } }); + + await page.getByTestId('call-stack-tab').click(); + + await expect(page.getByTestId('query-call-stack-empty')).toHaveText( + 'No Call Stack Found', + ); + }); +}); + +// eslint-disable-next-line playwright/no-skipped-test +test.skip('Call Stack with Running Workflow', () => { + test('should show call stack for running workflow', async ({ page }) => { + await page + .getByText('running-workflow') + .click({ position: { x: 0, y: 0 } }); + + await page.getByTestId('call-stack-tab').click(); + + await expect(page.getByTestId('query-call-stack')).toBeVisible(); + }); + + test('should handle errors when the call stack is not formatted as we expect', async ({ + page, + }) => { + await page + .getByText('running-workflow') + .click({ position: { x: 0, y: 0 } }); + + await page.getByTestId('call-stack-tab').click(); + + await mockQueryApiWithStackTraceError(page); + + await expect(page.getByTestId('query-call-stack')).toBeVisible(); + }); +}); diff --git a/tests/e2e/schedules.spec.ts b/tests/e2e/schedules.spec.ts new file mode 100644 index 000000000..2d3cb3254 --- /dev/null +++ b/tests/e2e/schedules.spec.ts @@ -0,0 +1,51 @@ +import { expect, test } from '@playwright/test'; + +test.beforeEach(async ({ page, baseURL }) => { + await page.goto(baseURL); +}); + +test.describe('Schedules Page', () => { + test('should render empty list of schedules and navigate to Create Schedule page with form', async ({ + page, + }, { + project: { + use: { isMobile }, + }, + }) => { + test.slow(); + // eslint-disable-next-line playwright/no-conditional-in-test + if (isMobile) { + await page.getByTestId('nav-menu-button').click(); + } + const scheduleButton = page + .getByTestId('schedules-button') + .locator('visible=true'); + await scheduleButton.click(); + await expect(page).toHaveURL(/schedules/); + const createScheduleButton = page.getByTestId('create-schedule'); + await expect(createScheduleButton).toBeVisible(); + await createScheduleButton.click(); + await expect(page).toHaveURL(/create/); + + await page.getByTestId('schedule-name-input').fill('e2e-schedule-1'); + await page.getByTestId('schedule-type-input').fill('test-type-e2e'); + await page.getByTestId('schedule-workflow-id-input').fill('e2e-1234'); + await page.getByTestId('schedule-task-queue-input').fill('default'); + await page + .locator('#schedule-payload-input') + .getByRole('textbox') + .fill('abc'); + await page.getByRole('textbox', { name: 'hrs' }).fill('1'); + const createSchedule = page.getByRole('button', { + name: 'Create Schedule', + }); + await expect(createSchedule).toBeDisabled(); + + await page.locator('#schedule-payload-input').getByRole('textbox').clear(); + await page + .locator('#schedule-payload-input') + .getByRole('textbox') + .fill('123'); + await expect(createSchedule).toBeEnabled(); + }); +}); diff --git a/tests/e2e/storageState.json b/tests/e2e/storageState.json new file mode 100644 index 000000000..f4ec35503 --- /dev/null +++ b/tests/e2e/storageState.json @@ -0,0 +1,4 @@ +{ + "cookies": [], + "origins": [] +} \ No newline at end of file diff --git a/tests/e2e/workflows-summary-table-configuration.spec.ts b/tests/e2e/workflows-summary-table-configuration.spec.ts new file mode 100644 index 000000000..a03cac4ce --- /dev/null +++ b/tests/e2e/workflows-summary-table-configuration.spec.ts @@ -0,0 +1,204 @@ +import { expect, Page, test } from '@playwright/test'; + +const initialHeaders = [ + 'Status', + 'Workflow ID', + 'Run ID', + 'Type', + 'Start', + 'End', +]; + +const reorderedHeaders = [ + 'Status', + 'Workflow ID', + 'Type', + 'Start', + 'End', + 'Run ID', +]; + +const headersToAdd = ['History Size', 'History Length', 'Execution Time']; +const headersToRemove = ['Type', 'Start', 'End']; + +test.beforeEach(async ({ page, baseURL }) => { + await page.goto(baseURL); +}); + +const delayClick = (testId: string, page: Page, delayMs = 100) => + new Promise((resolve, reject) => { + setTimeout(() => { + return page.getByTestId(testId).click().then(resolve).catch(reject); + }, delayMs); + }); + +test.describe('Workflows Table Configuration', () => { + test('allows adding columns to the table', async ({ page }) => { + await Promise.all( + initialHeaders.map((header) => + expect( + page.getByTestId(`workflows-summary-table-header-cell-${header}`), + ).toBeAttached(), + ), + ); + + await Promise.all( + headersToAdd.map((header) => + expect( + page.getByTestId(`workflows-summary-table-header-cell-${header}`), + ).not.toBeAttached(), + ), + ); + + await page + .getByTestId('workflows-summary-table-configuration-button') + .click(); + + for (const header of headersToAdd) { + await delayClick(`orderable-list-item-${header}-add-button`, page); + } + + await page.getByTestId('drawer-close-button').click(); + + await Promise.all( + headersToAdd.map((header) => + expect( + page.getByTestId(`workflows-summary-table-header-cell-${header}`), + ).toBeAttached(), + ), + ); + }); + + test('allows removing columns from the table', async ({ page }) => { + await Promise.all( + initialHeaders.map((header) => + expect( + page.getByTestId(`workflows-summary-table-header-cell-${header}`), + ).toBeAttached(), + ), + ); + + await page + .getByTestId('workflows-summary-table-configuration-button') + .click(); + + for (const header of headersToRemove) { + await delayClick(`orderable-list-item-${header}-remove-button`, page); + } + + await page.getByTestId('drawer-close-button').click(); + + await Promise.all( + headersToRemove.map((header) => + expect( + page.getByTestId(`workflows-summary-table-header-cell-${header}`), + ).not.toBeAttached(), + ), + ); + }); + + test('allows reordering columns in the table via buttons', async ({ + page, + }) => { + const initialThs = await page + .locator('.workflows-summary-table-header-cell') + .all(); + + await Promise.all( + initialThs.map(async (th, idx) => + expect(th).toHaveAttribute( + 'data-testid', + `workflows-summary-table-header-cell-${initialHeaders[idx]}`, + ), + ), + ); + + await page + .getByTestId('workflows-summary-table-configuration-button') + .click(); + + await expect( + page.locator('#workflows-table-configuration-drawer'), + ).toBeVisible(); + + for (let i = 0; i < 3; i++) { + await delayClick('orderable-list-item-Run ID-move-down-button', page); + } + + const reorderedThs = await page + .locator('.workflows-summary-table-header-cell') + .all(); + + await Promise.all( + reorderedThs.map((th, idx) => { + return expect(th).toHaveAttribute( + 'data-testid', + `workflows-summary-table-header-cell-${reorderedHeaders[idx]}`, + ); + }), + ); + }); + + test('allows reordering columns in the table via drag and drop', async ({ + page, + }, { + project: { + use: { isMobile }, + }, + }) => { + // eslint-disable-next-line playwright/no-skipped-test + test.skip(isMobile, 'This test is for Desktop only'); + + const initialThs = await page + .locator('.workflows-summary-table-header-cell') + .all(); + + await Promise.all( + initialThs.map(async (th, idx) => + expect(th).toHaveAttribute( + 'data-testid', + `workflows-summary-table-header-cell-${initialHeaders[idx]}`, + ), + ), + ); + + await page + .getByTestId('workflows-summary-table-configuration-button') + .click(); + + await expect( + page.locator('#workflows-table-configuration-drawer'), + ).toBeVisible(); + + const sourceElement = page.getByTestId('orderable-list-item-Run ID'); + const targetElement = page.getByTestId('orderable-list-item-End'); + const sourceBox = await sourceElement.boundingBox(); + const targetBox = await targetElement.boundingBox(); + + await page.mouse.move( + sourceBox.x + sourceBox.width / 2, + sourceBox.y + sourceBox.height / 2, + ); + await page.mouse.down(); + await page.mouse.move( + targetBox.x + targetBox.width / 2, + targetBox.y + targetBox.height / 2, + ); + await page.mouse.up(); + + await page.getByTestId('drawer-close-button').click(); + + const reorderedThs = await page + .locator('.workflows-summary-table-header-cell') + .all(); + + await Promise.all( + reorderedThs.map((th, idx) => { + return expect(th).toHaveAttribute( + 'data-testid', + `workflows-summary-table-header-cell-${reorderedHeaders[idx]}`, + ); + }), + ); + }); +}); diff --git a/tests/e2e/workflows.spec.ts b/tests/e2e/workflows.spec.ts new file mode 100644 index 000000000..0d34c1e09 --- /dev/null +++ b/tests/e2e/workflows.spec.ts @@ -0,0 +1,42 @@ +import { expect, test } from '@playwright/test'; + +test.beforeEach(async ({ page, baseURL }) => { + await page.goto(baseURL); +}); + +test.describe('Workflow Execution Page', () => { + test('should render decoded Payloads', async ({ page }) => { + test.slow(); + await page.getByRole('link', { name: 'e2e-workflow-1' }).click(); + + const inputAndResult = page.getByTestId('input-and-result'); + await expect(inputAndResult).toContainText('Plain text input 1'); + await expect(inputAndResult).toContainText('Received Plain text input 1'); + }); + + test('should render decoded call stack', async ({ page }) => { + await page.getByRole('link', { name: 'e2e-workflow-2' }).click(); + + await page.getByText('Call Stack').click(); + + const tab = page.getByTestId('call-stack-tab'); + await tab.click(); + + const codeBlock = page.getByRole('textbox'); + await expect(codeBlock).toContainText('at workflow'); + }); + + test('should render decoded query results', async ({ page }) => { + await page.getByRole('link', { name: 'e2e-workflow-2' }).click(); + + const tab = page.getByTestId('queries-tab'); + await tab.click(); + + await page.getByTestId('query-select-button').click(); + await page.getByRole('option', { name: 'is-blocked' }).click(); + await page.getByRole('button', { name: /query/i }).click(); + + const codeBlock = page.getByTestId('query-result').getByRole('textbox'); + await expect(codeBlock).toContainText('true'); + }); +}); diff --git a/tests/fixtures/completed-event-history.json b/tests/fixtures/completed-event-history.json new file mode 100644 index 000000000..67a63bc02 --- /dev/null +++ b/tests/fixtures/completed-event-history.json @@ -0,0 +1,268 @@ +[ + { + "eventId": "1", + "eventTime": "2022-04-28T05:52:26.878394493Z", + "eventType": "WorkflowExecutionStarted", + "version": "0", + "taskId": "1390698", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "RainbowStatusesWorkflow" + }, + "parentWorkflowNamespace": "", + "parentWorkflowExecution": null, + "parentInitiatedEventId": "0", + "taskQueue": { + "name": "rainbow-statuses", + "kind": "Normal" + }, + "input": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "Mg==" + } + ] + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "0s", + "workflowTaskTimeout": "10s", + "continuedExecutionRunId": "", + "initiator": "Unspecified", + "continuedFailure": null, + "lastCompletionResult": null, + "originalExecutionRunId": "63ef3750-fe4a-427d-848a-9ae30c0165cc", + "identity": "174278@user0@", + "firstExecutionRunId": "63ef3750-fe4a-427d-848a-9ae30c0165cc", + "retryPolicy": null, + "attempt": 1, + "workflowExecutionExpirationTime": null, + "cronSchedule": "", + "firstWorkflowTaskBackoff": "0s", + "memo": null, + "searchAttributes": { + "indexedFields": { + "CustomBoolField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "Qm9vbA==" + }, + "data": "ZmFsc2U=" + }, + "CustomDatetimeField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "RGF0ZXRpbWU=" + }, + "data": "IjIwMjItMDQtMjhUMDU6NTI6MjYuODc3NTQ5OTUzWiI=" + }, + "CustomDoubleField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "RG91Ymxl" + }, + "data": "MQ==" + }, + "CustomIntField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "SW50" + }, + "data": "MQ==" + }, + "CustomKeywordField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "S2V5d29yZA==" + }, + "data": "InJhaW5ib3ctc3RhdHVzZXMtNGRkZmVjIg==" + }, + "CustomStringField": { + "metadata": { + "encoding": "anNvbi9wbGFpbg==", + "type": "VGV4dA==" + }, + "data": "InJhaW5ib3cgc3RhdHVzZXMgNGRkZmVjIENvbXBsZXRlZCI=" + } + } + }, + "prevAutoResetPoints": null, + "header": { + "fields": {} + } + } + }, + { + "eventId": "2", + "eventTime": "2022-04-28T05:52:26.878411167Z", + "eventType": "WorkflowTaskScheduled", + "version": "0", + "taskId": "1390699", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "rainbow-statuses", + "kind": "Normal" + }, + "startToCloseTimeout": "10s", + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": "2022-04-28T05:52:26.884076144Z", + "eventType": "WorkflowTaskStarted", + "version": "0", + "taskId": "1390703", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "174128@user0@", + "requestId": "6700a890-14f9-4679-9d02-f5e861321bc9" + } + }, + { + "eventId": "4", + "eventTime": "2022-04-28T05:52:26.889067790Z", + "eventType": "WorkflowTaskCompleted", + "version": "0", + "taskId": "1390706", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "startedEventId": "3", + "identity": "174128@user0@", + "binaryChecksum": "abfe7e3fc78675a42c9e544a83eb7edb" + } + }, + { + "eventId": "5", + "eventTime": "2022-04-28T05:52:26.889109533Z", + "eventType": "ActivityTaskScheduled", + "version": "0", + "taskId": "1390707", + "activityTaskScheduledEventAttributes": { + "activityId": "5", + "activityType": { + "name": "CompletedActivity" + }, + "namespace": "", + "taskQueue": { + "name": "rainbow-statuses", + "kind": "Normal" + }, + "header": { + "fields": {} + }, + "input": null, + "scheduleToCloseTimeout": "0s", + "scheduleToStartTimeout": "0s", + "startToCloseTimeout": "3600s", + "heartbeatTimeout": "0s", + "workflowTaskCompletedEventId": "4", + "retryPolicy": { + "initialInterval": "1s", + "backoffCoefficient": 2, + "maximumInterval": "100s", + "maximumAttempts": 1, + "nonRetryableErrorTypes": [] + } + } + }, + { + "eventId": "6", + "eventTime": "2022-04-28T05:52:26.894480893Z", + "eventType": "ActivityTaskStarted", + "version": "0", + "taskId": "1390712", + "activityTaskStartedEventAttributes": { + "scheduledEventId": "5", + "identity": "174128@user0@", + "requestId": "be47e38b-8ebb-4e59-a1f7-1dbec3730c5b", + "attempt": 1, + "lastFailure": null + } + }, + { + "eventId": "7", + "eventTime": "2022-04-28T05:52:26.898301580Z", + "eventType": "ActivityTaskCompleted", + "version": "0", + "taskId": "1390713", + "activityTaskCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImhleSI=" + } + ] + }, + "scheduledEventId": "5", + "startedEventId": "6", + "identity": "174128@user0@" + } + }, + { + "eventId": "8", + "eventTime": "2022-04-28T05:52:26.898307162Z", + "eventType": "WorkflowTaskScheduled", + "version": "0", + "taskId": "1390714", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "user0:20715813-649f-4775-999f-5daba250d1d1", + "kind": "Sticky" + }, + "startToCloseTimeout": "10s", + "attempt": 1 + } + }, + { + "eventId": "9", + "eventTime": "2022-04-28T05:52:26.901843957Z", + "eventType": "WorkflowTaskStarted", + "version": "0", + "taskId": "1390718", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "8", + "identity": "174128@user0@", + "requestId": "0014a5cf-de0d-4ce8-b46a-e3f22968f800" + } + }, + { + "eventId": "10", + "eventTime": "2022-04-28T05:52:26.906189544Z", + "eventType": "WorkflowTaskCompleted", + "version": "0", + "taskId": "1390721", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "8", + "startedEventId": "9", + "identity": "174128@user0@", + "binaryChecksum": "abfe7e3fc78675a42c9e544a83eb7edb" + } + }, + { + "eventId": "11", + "eventTime": "2022-04-28T05:52:26.906219239Z", + "eventType": "WorkflowExecutionCompleted", + "version": "0", + "taskId": "1390722", + "workflowExecutionCompletedEventAttributes": { + "result": { + "payloads": [ + { + "metadata": { + "encoding": "anNvbi9wbGFpbg==" + }, + "data": "ImhleSI=" + } + ] + }, + "workflowTaskCompletedEventId": "10", + "newExecutionRunId": "" + } + } +] diff --git a/tests/global-setup.ts b/tests/global-setup.ts new file mode 100644 index 000000000..bde0c0742 --- /dev/null +++ b/tests/global-setup.ts @@ -0,0 +1,48 @@ +import { chromium, FullConfig } from '@playwright/test'; + +import { startWorkflows } from '../temporal/client'; +import { connect } from '../temporal/client'; +import { createCodecServer } from '../temporal/codec-server'; +import { runWorker } from '../temporal/workers'; +import { createTemporalServer } from '../utilities/temporal-server'; +import { createUIServer } from '../utilities/ui-server'; + +const setupDependencies = async () => { + const codecServer = await createCodecServer({ port: 8888 }); + const temporalServer = await createTemporalServer({ + codecEndpoint: 'http://127.0.0.1:8888', + }); + const uiServer = await createUIServer('e2e'); + + try { + await uiServer.ready(); + await codecServer.start(); + await temporalServer.ready(); + + const client = await connect(); + await runWorker(); + await startWorkflows(client); + } catch (e) { + console.log('Error setting up server: ', e); + } +}; + +async function globalSetup(config: FullConfig) { + const { mode } = config.metadata; + const { baseURL } = config.projects[0].use; + + const browser = await chromium.launch(); + const page = await browser.newPage(); + + if (mode === 'e2e') { + await setupDependencies(); + } + + await page.goto(baseURL); + + await page + .context() + .storageState({ path: `./tests/${mode}/storageState.json` }); +} + +export default globalSetup; diff --git a/tests/global-teardown.ts b/tests/global-teardown.ts new file mode 100644 index 000000000..ccd39f8f6 --- /dev/null +++ b/tests/global-teardown.ts @@ -0,0 +1,22 @@ +import { FullConfig } from '@playwright/test'; + +import { disconnect, stopWorkflows } from '../temporal/client'; +import { getCodecServer } from '../temporal/codec-server'; +import { stopWorker } from '../temporal/workers'; +import { getTemporalServer } from '../utilities/temporal-server'; +import { getUIServer } from '../utilities/ui-server'; + +export default async function (config: FullConfig) { + if (config.metadata.mode === 'e2e') { + const temporal = getTemporalServer(); + const codecServer = getCodecServer(); + const uiServer = getUIServer(); + + await stopWorkflows(); + await stopWorker(); + await disconnect(); + await codecServer.stop(); + await uiServer.shutdown(); + await temporal.shutdown(); + } +} diff --git a/tests/integration/archival-namespace.spec.ts b/tests/integration/archival-namespace.spec.ts new file mode 100644 index 000000000..cf4c7fdeb --- /dev/null +++ b/tests/integration/archival-namespace.spec.ts @@ -0,0 +1,39 @@ +import { expect, test } from '@playwright/test'; + +import { + mockNamespaceApi, + mockNamespaceApis, +} from '~/test-utilities/mock-apis'; + +const archivalWorkflowsUrl = '/namespaces/default/archival'; +let archived: boolean; + +test.beforeEach(async ({ page }) => { + await mockNamespaceApis(page); +}); + +test.describe('Archival - Archival disabled', () => { + test.beforeAll(() => { + archived = false; + }); + + test('it have the correct title on archival page', async ({ page }) => { + await mockNamespaceApi(page, archived); + await page.goto(archivalWorkflowsUrl); + const title = await page.getByTestId('archived-disabled-title').innerText(); + expect(title).toBe('This namespace is currently not enabled for archival.'); + }); +}); + +test.describe('Archival - Archival enabled', () => { + test.beforeAll(() => { + archived = true; + }); + + test('it have the correct title on archival page', async ({ page }) => { + await mockNamespaceApi(page, archived); + await page.goto(archivalWorkflowsUrl); + const title = await page.getByTestId('archived-enabled-title').innerText(); + expect(title).toBe('Archived Workflows'); + }); +}); diff --git a/tests/integration/dark-mode.desktop.spec.ts b/tests/integration/dark-mode.desktop.spec.ts new file mode 100644 index 000000000..674a2835b --- /dev/null +++ b/tests/integration/dark-mode.desktop.spec.ts @@ -0,0 +1,51 @@ +import { expect, test } from '@playwright/test'; + +import { mockWorkflowsApis } from '~/test-utilities/mock-apis'; +import '../test-utilities/custom-matchers'; + +test.describe('Dark Mode Dropdown', () => { + const localStorageKey = 'dark mode'; + + test.beforeEach(async ({ page }) => { + await mockWorkflowsApis(page); + await page.goto('/'); + }); + + test('user can select System Default option via dropdown menu', async ({ + page, + }) => { + const menuButton = page + .getByTestId('dark-mode-menu-button') + .locator('visible=true'); + await expect(menuButton).toBeVisible(); + + await menuButton.click(); + await page.getByRole('menuitem', { name: 'System Default' }).click(); + await expect(menuButton).toHaveAccessibleName('System Default'); + await expect(page).toHaveLocalStorageItem(localStorageKey, 'system'); + }); + + test('user can select Night option via dropdown menu', async ({ page }) => { + const menuButton = page + .getByTestId('dark-mode-menu-button') + .locator('visible=true'); + await expect(menuButton).toBeVisible(); + + await menuButton.click(); + await page.getByRole('menuitem', { name: 'Night' }).click(); + await expect(menuButton).toHaveAccessibleName('Night'); + await expect(page).toHaveLocalStorageItem(localStorageKey, true); + }); + + test('user can select Day option via dropdown menu', async ({ page }) => { + const menuButton = page + .getByTestId('dark-mode-menu-button') + .locator('visible=true'); + await expect(menuButton).toBeVisible(); + + await menuButton.click(); + await page.getByRole('menuitem', { name: 'Day' }).click(); + await expect(menuButton).toHaveAccessibleName('Day'); + await expect(page).toHaveLocalStorageItem(localStorageKey, false); + }); +}); diff --git a/tests/integration/dark-mode.mobile.spec.ts b/tests/integration/dark-mode.mobile.spec.ts new file mode 100644 index 000000000..10e193317 --- /dev/null +++ b/tests/integration/dark-mode.mobile.spec.ts @@ -0,0 +1,53 @@ +import { expect, test } from '@playwright/test'; + +import { mockWorkflowsApis } from '~/test-utilities/mock-apis'; +import '../test-utilities/custom-matchers'; + +test.describe('Dark Mode Dropdown on Mobile', () => { + const localStorageKey = 'dark mode'; + + test.beforeEach(async ({ page }) => { + await mockWorkflowsApis(page); + await page.goto('/'); + // Open the profile menu that contains the dark mode button + await page.getByTestId('nav-profile-button').click(); + }); + + test('user can select System Default option via dropdown menu', async ({ + page, + }) => { + const button = page + .getByTestId('dark-mode-menu-button') + .locator('visible=true'); + await expect(button).toBeVisible(); + + await button.click(); + await page.getByRole('menuitem', { name: 'System Default' }).click(); + await expect(button).toHaveAccessibleName('System Default'); + await expect(page).toHaveLocalStorageItem(localStorageKey, 'system'); + }); + + test('user can select Night option via dropdown menu', async ({ page }) => { + const button = page + .getByTestId('dark-mode-menu-button') + .locator('visible=true'); + await expect(button).toBeVisible(); + + await button.click(); + await page.getByRole('menuitem', { name: 'Night' }).click(); + await expect(button).toHaveAccessibleName('Night'); + await expect(page).toHaveLocalStorageItem(localStorageKey, true); + }); + + test('user can select Day option via dropdown menu', async ({ page }) => { + const button = page + .getByTestId('dark-mode-menu-button') + .locator('visible=true'); + await expect(button).toBeVisible(); + + await button.click(); + await page.getByRole('menuitem', { name: 'Day' }).click(); + await expect(button).toHaveAccessibleName('Day'); + await expect(page).toHaveLocalStorageItem(localStorageKey, false); + }); +}); diff --git a/tests/integration/data-encoder.desktop.spec.ts b/tests/integration/data-encoder.desktop.spec.ts new file mode 100644 index 000000000..c4300d58f --- /dev/null +++ b/tests/integration/data-encoder.desktop.spec.ts @@ -0,0 +1,249 @@ +import { expect, test } from '@playwright/test'; + +import { + mockSettingsApi, + mockWorkflowsApis, + waitForWorkflowsApis, +} from '~/test-utilities/mock-apis'; + +const workflowsUrl = '/namespaces/default/workflows'; + +test.describe('Data Encoder without Configuration Settings', () => { + test.beforeEach(async ({ page }) => { + await mockWorkflowsApis(page); + await page.goto(workflowsUrl); + await waitForWorkflowsApis(page, false); + }); + + test('Navigate to Data Encoder UI and configure Local Setting', async ({ + page, + }) => { + const dataEncoderStatusButton = page.getByTestId('data-encoder-status'); + await expect(dataEncoderStatusButton).toBeEnabled(); + await dataEncoderStatusButton.click(); + + const dataEncoderTitle = await page + .getByTestId('data-encoder-title') + .innerText(); + expect(dataEncoderTitle).toBe('Codec Server'); + + const dataEncoderConfirmButton = page.getByTestId( + 'confirm-data-encoder-button', + ); + await expect(dataEncoderConfirmButton).toBeEnabled(); + + await expect(page.locator('#data-encoder-endpoint-input')).toHaveValue(''); + + await page.locator('#data-encoder-endpoint-input').fill('localhost:8080'); + await expect(page.locator('.error-msg')).toHaveText( + 'Endpoint must start with http:// or https://', + ); + await expect(dataEncoderConfirmButton).toBeDisabled(); + + await page + .locator('#data-encoder-endpoint-input') + .fill('http://localhost:8080'); + await dataEncoderConfirmButton.click(); + + const dataEncoderStatusConfiguredButton = page.getByTestId( + 'data-encoder-status-configured', + ); + await expect(dataEncoderStatusConfiguredButton).toBeVisible(); + await dataEncoderStatusConfiguredButton.click(); + await expect(page.locator('#data-encoder-endpoint-input')).toHaveValue( + 'http://localhost:8080', + ); + }); + + test('Navigate to Data Encoder UI and configure and cancel Local Settings', async ({ + page, + }) => { + const dataEncoderStatusButton = page.getByTestId('data-encoder-status'); + await expect(dataEncoderStatusButton).toBeEnabled(); + await dataEncoderStatusButton.click(); + + const dataEncoderTitle = await page + .getByTestId('data-encoder-title') + .innerText(); + expect(dataEncoderTitle).toBe('Codec Server'); + + const dataEncoderConfirmButton = page.getByTestId( + 'confirm-data-encoder-button', + ); + await expect(dataEncoderConfirmButton).toBeEnabled(); + + await page + .locator('#data-encoder-endpoint-input') + .fill('https://localhost:8080'); + + await dataEncoderConfirmButton.click(); + + const dataEncoderStatusConfiguredButton = page.getByTestId( + 'data-encoder-status-configured', + ); + await expect(dataEncoderStatusConfiguredButton).toBeVisible(); + await dataEncoderStatusConfiguredButton.click(); + await expect(page.locator('#data-encoder-endpoint-input')).toHaveValue( + 'https://localhost:8080', + ); + await page + .locator('#data-encoder-endpoint-input') + .fill('http://localhost:9999'); + + const dataEncoderCancelButton = page.getByTestId( + 'cancel-data-encoder-button', + ); + await dataEncoderCancelButton.click(); + await expect(dataEncoderStatusConfiguredButton).toBeVisible(); + await dataEncoderStatusConfiguredButton.click(); + + await expect(page.locator('#data-encoder-endpoint-input')).toHaveValue( + 'https://localhost:8080', + ); + }); + + test('Navigate to Data Encoder UI and configure Local Settings with Pass Access Token', async ({ + page, + }) => { + const dataEncoderStatusButton = page.getByTestId('data-encoder-status'); + await expect(dataEncoderStatusButton).toBeEnabled(); + await dataEncoderStatusButton.click(); + + const dataEncoderConfirmButton = page.getByTestId( + 'confirm-data-encoder-button', + ); + await expect(dataEncoderConfirmButton).toBeEnabled(); + + await expect(page.locator('#data-encoder-endpoint-input')).toHaveValue(''); + + await page + .locator('#data-encoder-endpoint-input') + .fill('http://localhost:8080'); + + await page + .locator('label') + .filter({ hasText: 'Pass the user access token' }) + .click(); + + await expect(page.locator('.error-msg')).toHaveText( + 'Endpoint must be https:// if passing access token', + ); + await expect(dataEncoderConfirmButton).toBeDisabled(); + + await page + .locator('#data-encoder-endpoint-input') + .fill('https://localhost:8080'); + + await expect(dataEncoderConfirmButton).toBeEnabled(); + await dataEncoderConfirmButton.click(); + + const dataEncoderStatusConfiguredButton = page.getByTestId( + 'data-encoder-status-configured', + ); + await expect(dataEncoderStatusConfiguredButton).toBeVisible(); + await dataEncoderStatusConfiguredButton.click(); + await expect(page.locator('#data-encoder-endpoint-input')).toHaveValue( + 'https://localhost:8080', + ); + }); + + test('Navigate to Data Encoder UI and configure Local Settings with Include Credentials', async ({ + page, + }) => { + const dataEncoderStatusButton = page.getByTestId('data-encoder-status'); + await expect(dataEncoderStatusButton).toBeEnabled(); + await dataEncoderStatusButton.click(); + + const dataEncoderConfirmButton = page.getByTestId( + 'confirm-data-encoder-button', + ); + await expect(dataEncoderConfirmButton).toBeEnabled(); + + await expect(page.locator('#data-encoder-endpoint-input')).toHaveValue(''); + + await page + .locator('#data-encoder-endpoint-input') + .fill('http://localhost:8080'); + + await page + .locator('label') + .filter({ hasText: 'Include cross-origin credentials' }) + .click(); + + await dataEncoderConfirmButton.click(); + + const dataEncoderStatusConfiguredButton = page.getByTestId( + 'data-encoder-status-configured', + ); + await expect(dataEncoderStatusConfiguredButton).toBeVisible(); + await dataEncoderStatusConfiguredButton.click(); + await expect(page.locator('#data-encoder-endpoint-input')).toHaveValue( + 'http://localhost:8080', + ); + }); +}); + +test.describe('Data Encoder with Configuration Settings', () => { + test.beforeEach(async ({ page }) => { + await mockWorkflowsApis(page); + await mockSettingsApi(page, { + Codec: { + Endpoint: 'https://localhost:8081', + PassAccessToken: false, + IncludeCredentials: false, + }, + }); + await page.goto(workflowsUrl); + await waitForWorkflowsApis(page, false); + }); + + test('Navigate to Data Encoder UI', async ({ page }) => { + const dataEncoderStatusConfiguredButton = page.getByTestId( + 'data-encoder-status-configured', + ); + await expect(dataEncoderStatusConfiguredButton).toBeEnabled(); + await dataEncoderStatusConfiguredButton.click(); + const dataEncoderTitle = await page + .getByTestId('data-encoder-title') + .innerText(); + expect(dataEncoderTitle).toBe('Codec Server'); + + await expect(page.getByTestId('override-accordion')).toHaveText( + /Use Cluster-level setting, where available/, + ); + + const dataEncoderConfirmButton = page.getByTestId( + 'confirm-data-encoder-button', + ); + await expect(dataEncoderConfirmButton).toBeEnabled(); + + await expect(page.locator('#data-encoder-endpoint-input')).toHaveValue(''); + + await page + .locator('#data-encoder-endpoint-input') + .fill('http://localhost:8080'); + + await page.getByTestId('override-accordion').click(); + await page.getByTestId('use-local-endpoint-input').click(); + + await page + .getByRole('button', { + name: 'Use my browser setting and ignore Cluster-level setting.', + }) + .click(); + + await expect(page.getByTestId('override-accordion')).toHaveText( + /Use my browser setting and ignore Cluster-level setting/, + ); + + await dataEncoderConfirmButton.click(); + + await expect(dataEncoderStatusConfiguredButton).toBeEnabled(); + + await dataEncoderStatusConfiguredButton.click(); + + await expect(page.getByTestId('override-accordion')).toHaveText( + /Use my browser setting and ignore Cluster-level setting/, + ); + }); +}); diff --git a/tests/integration/data-encoder.mobile.spec.ts b/tests/integration/data-encoder.mobile.spec.ts new file mode 100644 index 000000000..f19d077a4 --- /dev/null +++ b/tests/integration/data-encoder.mobile.spec.ts @@ -0,0 +1,285 @@ +import { expect, test } from '@playwright/test'; + +import { + mockSettingsApi, + mockWorkflowsApis, + waitForWorkflowsApis, +} from '~/test-utilities/mock-apis'; + +const workflowsUrl = '/namespaces/default/workflows'; + +test.describe('Data Encoder without Configuration Settings', () => { + test.beforeEach(async ({ page }) => { + await mockWorkflowsApis(page); + await page.goto(workflowsUrl); + await waitForWorkflowsApis(page, false); + await page.getByTestId('nav-profile-button').click(); + }); + + test('Navigate to Data Encoder UI and configure Local Setting', async ({ + page, + }) => { + const dataEncoderStatusButton = page + .getByTestId('data-encoder-status') + .locator('visible=true'); + await expect(dataEncoderStatusButton).not.toHaveClass('disabled'); + await dataEncoderStatusButton.click(); + + const dataEncoderTitle = await page + .getByTestId('data-encoder-title') + .locator('visible=true') + .innerText(); + expect(dataEncoderTitle).toBe('Codec Server'); + + const dataEncoderConfirmButton = page + .getByTestId('confirm-data-encoder-button') + .locator('visible=true'); + await expect(dataEncoderConfirmButton).toBeEnabled(); + + await expect( + page.locator('#data-encoder-endpoint-input').locator('visible=true'), + ).toHaveValue(''); + + await page + .locator('#data-encoder-endpoint-input') + .locator('visible=true') + .fill('localhost:8080'); + await expect(page.locator('.error-msg').locator('visible=true')).toHaveText( + 'Endpoint must start with http:// or https://', + ); + await expect(dataEncoderConfirmButton).toBeDisabled(); + + await page + .locator('#data-encoder-endpoint-input') + .locator('visible=true') + .fill('http://localhost:8080'); + await dataEncoderConfirmButton.click(); + + const dataEncoderStatusConfiguredButton = page + .getByTestId('data-encoder-status-configured') + .locator('visible=true'); + await dataEncoderStatusConfiguredButton.click(); + await expect( + page.locator('#data-encoder-endpoint-input').locator('visible=true'), + ).toHaveValue('http://localhost:8080'); + }); + + test('Navigate to Data Encoder UI and configure and cancel Local Settings', async ({ + page, + }) => { + const dataEncoderStatusButton = page + .getByTestId('data-encoder-status') + .locator('visible=true'); + await expect(dataEncoderStatusButton).not.toHaveClass('disabled'); + await dataEncoderStatusButton.click(); + + const dataEncoderTitle = await page + .getByTestId('data-encoder-title') + .locator('visible=true') + .innerText(); + expect(dataEncoderTitle).toBe('Codec Server'); + + const dataEncoderConfirmButton = page + .getByTestId('confirm-data-encoder-button') + .locator('visible=true'); + await expect(dataEncoderConfirmButton).toBeEnabled(); + + await page + .locator('#data-encoder-endpoint-input') + .locator('visible=true') + .fill('https://localhost:8080'); + + await dataEncoderConfirmButton.click(); + + const dataEncoderStatusConfiguredButton = page + .getByTestId('data-encoder-status-configured') + .locator('visible=true'); + await dataEncoderStatusConfiguredButton.click(); + await expect( + page.locator('#data-encoder-endpoint-input').locator('visible=true'), + ).toHaveValue('https://localhost:8080'); + await page + .locator('#data-encoder-endpoint-input') + .locator('visible=true') + .fill('http://localhost:9999'); + + const dataEncoderCancelButton = page + .getByTestId('cancel-data-encoder-button') + .locator('visible=true'); + await dataEncoderCancelButton.click(); + await expect(dataEncoderStatusConfiguredButton).toBeVisible(); + await dataEncoderStatusConfiguredButton.click(); + + await expect( + page.locator('#data-encoder-endpoint-input').locator('visible=true'), + ).toHaveValue('https://localhost:8080'); + }); + + test('Navigate to Data Encoder UI and configure Local Settings with Pass Access Token', async ({ + page, + }) => { + const dataEncoderStatusButton = page + .getByTestId('data-encoder-status') + .locator('visible=true'); + await expect(dataEncoderStatusButton).not.toHaveClass('disabled'); + await dataEncoderStatusButton.click(); + + const dataEncoderConfirmButton = page + .getByTestId('confirm-data-encoder-button') + .locator('visible=true'); + await expect(dataEncoderConfirmButton).toBeEnabled(); + + await expect( + page.locator('#data-encoder-endpoint-input').locator('visible=true'), + ).toHaveValue(''); + + await page + .locator('#data-encoder-endpoint-input') + .locator('visible=true') + .fill('http://localhost:8080'); + + await page + .locator('label') + .filter({ hasText: 'Pass the user access token' }) + .locator('visible=true') + .click(); + + await expect(page.locator('.error-msg').locator('visible=true')).toHaveText( + 'Endpoint must be https:// if passing access token', + ); + await expect(dataEncoderConfirmButton).toBeDisabled(); + + await page + .locator('#data-encoder-endpoint-input') + .locator('visible=true') + .fill('https://localhost:8080'); + + await expect(dataEncoderConfirmButton).toBeEnabled(); + await dataEncoderConfirmButton.click(); + + const dataEncoderStatusConfiguredButton = page + .getByTestId('data-encoder-status-configured') + .locator('visible=true'); + await dataEncoderStatusConfiguredButton.click(); + await expect( + page.locator('#data-encoder-endpoint-input').locator('visible=true'), + ).toHaveValue('https://localhost:8080'); + }); + + test('Navigate to Data Encoder UI and configure Local Settings with Include Credentials', async ({ + page, + }) => { + const dataEncoderStatusButton = page + .getByTestId('data-encoder-status') + .locator('visible=true'); + await expect(dataEncoderStatusButton).not.toHaveClass('disabled'); + await dataEncoderStatusButton.click(); + + const dataEncoderConfirmButton = page + .getByTestId('confirm-data-encoder-button') + .locator('visible=true'); + await expect(dataEncoderConfirmButton).toBeEnabled(); + + await expect( + page.locator('#data-encoder-endpoint-input').locator('visible=true'), + ).toHaveValue(''); + + await page + .locator('#data-encoder-endpoint-input') + .locator('visible=true') + .fill('http://localhost:8080'); + + await page + .locator('label') + .filter({ hasText: 'Include cross-origin credentials' }) + .locator('visible=true') + .click(); + + await dataEncoderConfirmButton.click(); + + const dataEncoderStatusConfiguredButton = page + .getByTestId('data-encoder-status-configured') + .locator('visible=true'); + await dataEncoderStatusConfiguredButton.click(); + await expect( + page.locator('#data-encoder-endpoint-input').locator('visible=true'), + ).toHaveValue('http://localhost:8080'); + }); +}); + +test.describe('Data Encoder with Configuration Settings', () => { + test.beforeEach(async ({ page }) => { + await mockWorkflowsApis(page); + await mockSettingsApi(page, { + Codec: { + Endpoint: 'https://localhost:8081', + PassAccessToken: false, + IncludeCredentials: false, + }, + }); + await page.goto(workflowsUrl); + await waitForWorkflowsApis(page, false); + await page.getByTestId('nav-profile-button').click(); + }); + + test('Navigate to Data Encoder UI', async ({ page }) => { + const dataEncoderStatusConfiguredButton = page + .getByTestId('data-encoder-status-configured') + .locator('visible=true'); + await expect(dataEncoderStatusConfiguredButton).not.toHaveClass('disabled'); + await dataEncoderStatusConfiguredButton.click(); + const dataEncoderTitle = await page + .getByTestId('data-encoder-title') + .locator('visible=true') + .innerText(); + expect(dataEncoderTitle).toBe('Codec Server'); + + await expect( + page.getByTestId('override-accordion').locator('visible=true'), + ).toHaveText(/Use Cluster-level setting, where available/); + + const dataEncoderConfirmButton = page + .getByTestId('confirm-data-encoder-button') + .locator('visible=true'); + await expect(dataEncoderConfirmButton).toBeEnabled(); + + await expect( + page.locator('#data-encoder-endpoint-input').locator('visible=true'), + ).toHaveValue(''); + + await page + .locator('#data-encoder-endpoint-input') + .locator('visible=true') + .fill('http://localhost:8080'); + + await page + .getByTestId('override-accordion') + .locator('visible=true') + .click(); + await page + .getByTestId('use-local-endpoint-input') + .locator('visible=true') + .click(); + + await page + .getByRole('button', { + name: 'Use my browser setting and ignore Cluster-level setting.', + }) + .locator('visible=true') + .click(); + + await expect( + page.getByTestId('override-accordion').locator('visible=true'), + ).toHaveText(/Use my browser setting and ignore Cluster-level setting/); + + await dataEncoderConfirmButton.click(); + + await expect(dataEncoderStatusConfiguredButton).toBeEnabled(); + + await dataEncoderStatusConfiguredButton.click(); + + await expect( + page.getByTestId('override-accordion').locator('visible=true'), + ).toHaveText(/Use my browser setting and ignore Cluster-level setting/); + }); +}); diff --git a/tests/integration/disable-write-actions.spec.ts b/tests/integration/disable-write-actions.spec.ts new file mode 100644 index 000000000..0147ed4c3 --- /dev/null +++ b/tests/integration/disable-write-actions.spec.ts @@ -0,0 +1,57 @@ +import { expect, test } from '@playwright/test'; + +import { mockSettingsApi, mockWorkflowApis } from '~/test-utilities/mock-apis'; +import { mockSchedulesApis } from '~/test-utilities/mock-apis'; +import { mockWorkflow } from '~/test-utilities/mocks/workflow'; + +const workflowUrl = `/namespaces/default/workflows/${mockWorkflow.workflowExecutionInfo.execution.workflowId}/${mockWorkflow.workflowExecutionInfo.execution.runId}/history`; +const schedulesUrl = '/namespaces/default/schedules'; + +test.describe('Enable write actions by default', () => { + test.beforeEach(async ({ page }) => { + await page.goto(workflowUrl); + await mockWorkflowApis(page); + }); + + test('Cancel workflow button enabled', async ({ page }) => { + const workflowActionButton = page + .locator('#workflow-actions-primary-button') + .locator('visible=true'); + await expect(workflowActionButton).toBeEnabled(); + }); +}); + +test.describe('Disable write actions on workflow actions', () => { + test.beforeEach(async ({ page }) => { + await page.goto(workflowUrl); + await mockWorkflowApis(page); + await mockSettingsApi(page, { DisableWriteActions: true }); + }); + + test('Cancel workflow button disabled', async ({ page }) => { + const workflowActionButton = page + .locator('#workflow-actions-primary-button') + .locator('visible=true'); + await expect(workflowActionButton).toBeDisabled(); + }); +}); + +test.describe('Disable write actions on empty schedules list actions', () => { + test.beforeEach(async ({ page }) => { + await page.goto(schedulesUrl); + await mockSchedulesApis(page, true, true); + await mockSettingsApi(page, { DisableWriteActions: true }); + }); + + test('Create Schedule button is disabled', async ({ page }) => { + await page.goto(schedulesUrl); + + const namespace = await page.locator('h1').innerText(); + console.log('Namespace:', namespace); + expect(namespace).toBe('0 Schedules'); + console.log('Namespace:after', namespace); + const createButton = page.getByTestId('create-schedule'); + await expect(createButton).toBeDisabled(); + console.log('Namespace:afterafter', namespace); + }); +}); diff --git a/tests/integration/import-event-history.spec.ts b/tests/integration/import-event-history.spec.ts new file mode 100644 index 000000000..66f38ab86 --- /dev/null +++ b/tests/integration/import-event-history.spec.ts @@ -0,0 +1,92 @@ +import { expect, test } from '@playwright/test'; + +import { + mockWorkflowsApis, + mockWorkflowsGroupByCountApi, + SETTINGS_API, + waitForWorkflowsApis, +} from '~/test-utilities/mock-apis'; + +const importUrl = '/import/events'; +const importEventHistoryUrl = + '/import/events/default/workflow/run/history/feed'; +const workflowsUrl = '/namespaces/default/workflows'; + +test.beforeEach(async ({ page }) => { + await mockWorkflowsApis(page); +}); + +test('Navigate to import page from nav', async ({ page }, { + project: { + use: { isMobile }, + }, +}) => { + await mockWorkflowsGroupByCountApi(page); + await page.goto(workflowsUrl); + await waitForWorkflowsApis(page); + + const count = await page.getByTestId('workflow-count').innerText(); + expect(count).toBe('31,230'); + + await page.goto(importUrl); + page.waitForRequest(SETTINGS_API); + + // eslint-disable-next-line playwright/no-conditional-in-test + if (isMobile) { + await page.getByTestId('nav-menu-button').click(); + } + await page.getByTestId('import-button').locator('visible=true').click(); + + const title = await page.getByTestId('import-event-history').innerText(); + expect(title).toBe('Import Event History'); + + const importButton = page.getByRole('button', { name: 'Import' }); + await expect(importButton).toBeDisabled(); +}); + +test('Navigate to import page directly and upload a json file for event history import', async ({ + page, +}) => { + await page.goto(importUrl); + page.waitForRequest(SETTINGS_API); + + const title = await page.getByTestId('import-event-history').innerText(); + expect(title).toBe('Import Event History'); + + const importButton = page.getByRole('button', { name: 'Import' }); + await expect(importButton).toBeDisabled(); + + const fileUploadButton = page.locator('input[type="file"]'); + await fileUploadButton.setInputFiles( + './tests/fixtures/completed-event-history.json', + ); + + await expect(importButton).toBeEnabled(); + await importButton.click(); + + const table = page.locator('table'); + await expect(table).toBeVisible(); +}); + +test('Navigate to import event history page directly to import event history', async ({ + page, +}) => { + await page.goto(importEventHistoryUrl); + page.waitForRequest(SETTINGS_API); + + const table = page.locator('table'); + await expect(table).toBeVisible(); + + const importButton = page.getByRole('button', { name: 'Import' }); + await expect(importButton).toBeDisabled(); + + const fileUploadButton = page.locator('input[type="file"]'); + await fileUploadButton.setInputFiles( + './tests/fixtures/completed-event-history.json', + ); + + await expect(importButton).toBeEnabled(); + await importButton.click(); + + await expect(table).toBeVisible(); +}); diff --git a/tests/integration/schedule-edit.spec.ts b/tests/integration/schedule-edit.spec.ts new file mode 100644 index 000000000..adc03257d --- /dev/null +++ b/tests/integration/schedule-edit.spec.ts @@ -0,0 +1,58 @@ +import { expect, test } from '@playwright/test'; + +import { mockScheduleApi, mockSchedulesApis } from '~/test-utilities/mock-apis'; + +const schedulesUrl = '/namespaces/default/schedules'; +const scheduleEditUrl = '/namespaces/default/schedules/test-schedule/edit'; + +test.describe('Schedules List with schedules', () => { + test.beforeEach(async ({ page }) => { + await mockSchedulesApis(page); + await mockScheduleApi(page); + }); + + test('selects schedule and edits', async ({ page }) => { + await page.goto(schedulesUrl); + + const createButton = page.getByTestId('create-schedule'); + await expect(createButton).toBeEnabled(); + + const scheduleLink = page.getByRole('link', { name: /test-schedule/i }); + await expect(scheduleLink).toBeVisible(); + await scheduleLink.click(); + + await expect(page.getByTestId('schedule-name')).toBeVisible(); + await expect(page.getByTestId('schedule-name')).toHaveText('test-schedule'); + + const scheduleActions = page.getByLabel('Schedule Actions'); + await expect(scheduleActions).toBeVisible(); + await scheduleActions.click(); + + const editButton = page.getByTestId('edit-schedule'); + await expect(editButton).toBeVisible(); + await editButton.click(); + }); + + test('Edits existing schedule', async ({ page }) => { + await page.goto(scheduleEditUrl); + + const heading = await page.locator('h1').innerText(); + expect(heading).toBe('Edit Schedule'); + + const typeInput = page.getByTestId('schedule-type-input'); + await expect(typeInput).toBeVisible(); + await expect(typeInput).toHaveValue('run-regularly'); + const workflowInput = page.getByTestId('schedule-workflow-id-input'); + await expect(workflowInput).toBeVisible(); + await expect(workflowInput).toHaveValue('test123'); + await workflowInput.fill('new-workflow-id'); + const taskQueueInput = page.getByTestId('schedule-task-queue-input'); + await expect(taskQueueInput).toBeVisible(); + await expect(taskQueueInput).toHaveValue('test'); + + const submitButton = page.getByTestId('create-schedule-button'); + await expect(submitButton).toBeVisible(); + await expect(submitButton).toBeEnabled(); + await submitButton.click(); + }); +}); diff --git a/tests/integration/schedules-create.spec.ts b/tests/integration/schedules-create.spec.ts new file mode 100644 index 000000000..434191390 --- /dev/null +++ b/tests/integration/schedules-create.spec.ts @@ -0,0 +1,89 @@ +import { expect, test } from '@playwright/test'; + +import { mockSchedulesApis } from '~/test-utilities/mock-apis'; + +const createScheduleUrl = '/namespaces/default/schedules/create'; + +test.describe('Creates Schedule Successfully', () => { + test.beforeEach(async ({ page }) => { + await mockSchedulesApis(page, true, false, { + customAttributes: { + attributeOne: 'Keyword', + attributeTwo: 'Keyword', + }, + }); + await page.goto(createScheduleUrl); + }); + + test('fills out interval-based schedule and submits', async ({ page }) => { + await page.getByTestId('schedule-name-input').fill('test'); + await page.getByTestId('schedule-type-input').fill('test'); + await page.getByTestId('schedule-workflow-id-input').fill('test'); + await page.getByTestId('schedule-task-queue-input').fill('test'); + await page.getByTestId('interval-tab').click(); + await page.getByTestId('days-input').fill('1'); + await page.getByTestId('hour-interval-input').fill('2'); + await page.getByTestId('minute-interval-input').fill('30'); + await page.getByTestId('second-interval-input').fill('0'); + + const createButton = page.getByTestId('create-schedule-button'); + await expect(createButton).toBeEnabled(); + await createButton.click(); + + await expect(page.getByText('Creating Schedule...')).toBeVisible(); + }); + + test('fills out schedule with custom search attributes and submits', async ({ + page, + }) => { + await page.getByTestId('schedule-name-input').fill('test'); + await page.getByTestId('schedule-type-input').fill('test'); + await page.getByTestId('schedule-workflow-id-input').fill('test'); + await page.getByTestId('schedule-task-queue-input').fill('test'); + await page.getByTestId('interval-tab').click(); + await page.getByTestId('days-input').fill('1'); + await page.getByTestId('hour-interval-input').fill('2'); + await page.getByTestId('minute-interval-input').fill('30'); + await page.getByTestId('second-interval-input').fill('0'); + + await page.getByTestId('workflows-tab').click(); + const workflowsTab = page.getByTestId('workflows-panel'); + await expect( + workflowsTab.getByTestId('add-search-attribute-button'), + ).toBeEnabled(); + await workflowsTab.getByTestId('add-search-attribute-button').click(); + await expect( + workflowsTab.getByTestId('search-attribute-select-button'), + ).toBeEnabled(); + await workflowsTab.getByTestId('search-attribute-select-button').click(); + await expect( + workflowsTab.getByRole('option', { name: 'attributeOne' }), + ).toBeVisible(); + await workflowsTab.getByRole('option', { name: 'attributeOne' }).click(); + await workflowsTab + .getByTestId('custom-search-attribute-value') + .fill('workflow-value'); + + await page.getByTestId('schedule-tab').click(); + const scheduleTab = page.getByTestId('schedule-panel'); + await expect( + scheduleTab.getByTestId('add-search-attribute-button'), + ).toBeEnabled(); + await scheduleTab.getByTestId('add-search-attribute-button').click(); + await expect( + scheduleTab.getByTestId('search-attribute-select-button'), + ).toBeEnabled(); + await scheduleTab.getByTestId('search-attribute-select-button').click(); + + await scheduleTab.getByRole('option', { name: 'attributeTwo' }).click(); + await scheduleTab + .getByTestId('custom-search-attribute-value') + .fill('schedule-value'); + + const createButton = page.getByTestId('create-schedule-button'); + await expect(createButton).toBeEnabled(); + await createButton.click(); + + await expect(page.getByText('Creating Schedule...')).toBeVisible(); + }); +}); diff --git a/tests/integration/schedules-list.spec.ts b/tests/integration/schedules-list.spec.ts new file mode 100644 index 000000000..3255878b1 --- /dev/null +++ b/tests/integration/schedules-list.spec.ts @@ -0,0 +1,41 @@ +import { expect, test } from '@playwright/test'; + +import { mockSchedulesApis } from '~/test-utilities/mock-apis'; + +const schedulesUrl = '/namespaces/default/schedules'; + +test.describe('Schedules List with no schedules', () => { + test.beforeEach(async ({ page }) => { + await mockSchedulesApis(page, true); + }); + + test('it displays enabled Create Schedule button with no schedules', async ({ + page, + }) => { + await page.goto(schedulesUrl); + + const namespace = await page.locator('h1').innerText(); + expect(namespace).toBe('0 Schedules'); + + const createButton = page.getByTestId('create-schedule'); + await expect(createButton).toBeEnabled(); + }); +}); + +test.describe('Schedules List with schedules', () => { + test.beforeEach(async ({ page }) => { + await mockSchedulesApis(page); + }); + + test('it displays enabled Create Schedule button with no schedules', async ({ + page, + }) => { + await page.goto(schedulesUrl); + + const namespace = await page.locator('h1').innerText(); + expect(namespace).toBe('0 Schedules'); + + const createButton = page.getByTestId('create-schedule'); + await expect(createButton).toBeEnabled(); + }); +}); diff --git a/tests/integration/start-a-workflow.spec.ts b/tests/integration/start-a-workflow.spec.ts new file mode 100644 index 000000000..841fbf3e3 --- /dev/null +++ b/tests/integration/start-a-workflow.spec.ts @@ -0,0 +1,55 @@ +import { expect, test } from '@playwright/test'; + +import { + mockGlobalApis, + mockSearchAttributesApi, + mockSettingsApi, + mockTaskQueuesApi, +} from '~/test-utilities/mock-apis'; + +test.describe('Start a Workflow', () => { + const startWorkflowUrl = '/namespaces/default/workflows/start-workflow'; + test.beforeEach(async ({ page }) => { + await mockGlobalApis(page); + }); + + test.describe('Start a Workflow - Disabled', () => { + test('redirects to Workflow list page when start workflow is disabled via Settings API', async ({ + page, + }) => { + await page.goto(startWorkflowUrl); + await expect(page).toHaveURL( + '/namespaces/default/workflows/start-workflow', + ); + const navigationPromise = page.waitForURL('namespaces/default/workflows'); + await navigationPromise; + await expect(page).toHaveURL('/namespaces/default/workflows'); + }); + }); + + test.describe('Start a Workflow - Enabled', () => { + test('Shows blank Start a Workflow form', async ({ page }) => { + await mockSettingsApi(page, { StartWorkflowDisabled: false }); + await mockSearchAttributesApi(page); + await page.goto(startWorkflowUrl); + await expect(page).toHaveURL( + '/namespaces/default/workflows/start-workflow', + ); + await expect(page.getByTestId('start-workflow')).toHaveText( + 'Start a Workflow', + ); + await expect(page.getByTestId('start-workflow-button')).toBeDisabled(); + await expect(page.locator('#workflowId')).toBeEnabled(); + await expect(page.locator('#taskQueue')).toBeEnabled(); + await expect(page.locator('#workflowType')).toBeEnabled(); + await page.locator('#workflowId').type('test-workflow-id'); + await page.locator('#taskQueue').type('task-queue'); + await mockTaskQueuesApi(page); + await page.locator('#workflowType').type('test-workflow-type'); + await expect(page.getByRole('status')).toContainText( + 'Task Queue is Active', + ); + await expect(page.getByTestId('start-workflow-button')).toBeEnabled(); + }); + }); +}); diff --git a/tests/integration/storageState.json b/tests/integration/storageState.json new file mode 100644 index 000000000..f4ec35503 --- /dev/null +++ b/tests/integration/storageState.json @@ -0,0 +1,4 @@ +{ + "cookies": [], + "origins": [] +} \ No newline at end of file diff --git a/tests/integration/workflow-actions.spec.ts b/tests/integration/workflow-actions.spec.ts new file mode 100644 index 000000000..4bd3b00c5 --- /dev/null +++ b/tests/integration/workflow-actions.spec.ts @@ -0,0 +1,542 @@ +import { expect, test } from '@playwright/test'; + +import { ResetReapplyExcludeType, ResetReapplyType } from '$src/lib/types'; +import { + mockClusterApi, + mockEventHistoryApi, + mockSettingsApi, + mockWorkflowApis, +} from '~/test-utilities/mock-apis'; +import { mockContinuedAsNewEventHistory } from '~/test-utilities/mocks/event-history'; +import { + mockCompletedWorkflow, + mockWorkflow, + mockWorkflowResetApi, + WORKFLOW_RESET_API, + WORKFLOW_TERMINATE_API, +} from '~/test-utilities/mocks/workflow'; + +test.describe('Workflow Actions for a Completed Workflow', () => { + const { + workflowExecutionInfo: { + execution: { workflowId, runId }, + }, + } = mockCompletedWorkflow; + + const workflowUrl = `/namespaces/default/workflows/${workflowId}/${runId}/history`; + + test.describe('Workflow Reset for server version <1.24', () => { + test.beforeEach(async ({ page }) => { + await page.goto(workflowUrl); + + await mockWorkflowApis(page, mockCompletedWorkflow); + await mockWorkflowResetApi(page); + }); + + test('is disabled when global write actions are disabled via Settings API', async ({ + page, + }) => { + await mockSettingsApi(page, { DisableWriteActions: true }); + + await expect( + page.locator('#workflow-actions-menu-button').locator('visible=true'), + ).toBeEnabled(); + + await expect( + page + .locator('#workflow-actions-primary-button') + .locator('visible=true'), + ).toBeDisabled(); + }); + + test('is disabled when reset is disabled via Settings API', async ({ + page, + }) => { + await mockSettingsApi(page, { WorkflowResetDisabled: true }); + + await expect( + page.locator('#workflow-actions-menu-button').locator('visible=true'), + ).toBeEnabled(); + + await expect( + page + .locator('#workflow-actions-primary-button') + .locator('visible=true'), + ).toBeDisabled(); + }); + + test('allows reapplying signals after the reset point', async ({ + page, + }) => { + const requestPromise = page.waitForRequest(WORKFLOW_RESET_API); + + await expect( + page.locator('#workflow-actions-menu-button').locator('visible=true'), + ).toBeEnabled(); + + await expect( + page + .locator('#workflow-actions-primary-button') + .locator('visible=true'), + ).toBeEnabled(); + + await page + .locator('#workflow-actions-primary-button') + .locator('visible=true') + .click(); + + await page + .getByTestId('workflow-reset-event-id-select-button') + .locator('visible=true') + .click(); + await page + .locator('#reset-event-id-select') + .locator('[role="option"]') + .locator('visible=true') + .first() + .click(); + + await page + .getByTestId('reset-confirmation-modal') + .locator('visible=true') + .getByTestId('confirm-modal-button') + .click(); + + const request = await requestPromise; + const body = request.postDataJSON(); + + expect(body.resetReapplyType).toBe( + ResetReapplyType.RESET_REAPPLY_TYPE_SIGNAL, + ); + }); + + test('allows NOT reapplying signals after the reset point', async ({ + page, + }) => { + const requestPromise = page.waitForRequest(WORKFLOW_RESET_API); + + await expect( + page.locator('#workflow-actions-menu-button').locator('visible=true'), + ).toBeEnabled(); + + await expect( + page + .locator('#workflow-actions-primary-button') + .locator('visible=true'), + ).toBeEnabled(); + + await page + .locator('#workflow-actions-primary-button') + .locator('visible=true') + .click(); + + await page + .getByTestId('workflow-reset-event-id-select-button') + .locator('visible=true') + .click(); + + await page + .locator('#reset-event-id-select') + .locator('[role="option"]') + .locator('visible=true') + .first() + .click(); + + // this checkbox is defaulted to checked, so click it to uncheck it + await page + .getByTestId('reset-include-signals-checkbox') + .locator('visible=true') + .click(); + + await page + .getByTestId('reset-confirmation-modal') + .locator('visible=true') + .getByTestId('confirm-modal-button') + .click(); + + const request = await requestPromise; + const body = request.postDataJSON(); + + expect(body.resetReapplyType).toBe( + ResetReapplyType.RESET_REAPPLY_TYPE_NONE, + ); + }); + }); + + test.describe('Workflow Reset for server version >=1.24', () => { + test.beforeEach(async ({ page }) => { + await mockWorkflowApis(page, mockCompletedWorkflow); + await mockClusterApi(page, { serverVersion: '1.24.0' }); + await mockWorkflowResetApi(page); + await page.goto(workflowUrl); + }); + + test('Allows reapplying Signals and Updates after the reset point', async ({ + page, + }) => { + const requestPromise = page.waitForRequest(WORKFLOW_RESET_API); + + await expect( + page.locator('#workflow-actions-menu-button').locator('visible=true'), + ).toBeEnabled(); + + await expect( + page + .locator('#workflow-actions-primary-button') + .locator('visible=true'), + ).toBeEnabled(); + + await page + .locator('#workflow-actions-primary-button') + .locator('visible=true') + .click(); + + await page + .getByTestId('workflow-reset-event-id-select-button') + .locator('visible=true') + .click(); + + await page + .locator('#reset-event-id-select') + .locator('[role="option"]') + .locator('visible=true') + .first() + .click(); + + await page + .getByTestId('reset-confirmation-modal') + .locator('visible=true') + .getByTestId('confirm-modal-button') + .click(); + + const request = await requestPromise; + const body = request.postDataJSON(); + + expect(body.resetReapplyExcludeTypes).toStrictEqual([ + ResetReapplyExcludeType.RESET_REAPPLY_EXCLUDE_TYPE_UNSPECIFIED, + ]); + + expect(body.resetReapplyType).toBe(3); + }); + + test('Allows excluding Signals after the reset point', async ({ page }) => { + const requestPromise = page.waitForRequest(WORKFLOW_RESET_API); + + await expect( + page.locator('#workflow-actions-menu-button').locator('visible=true'), + ).toBeEnabled(); + + await expect( + page + .locator('#workflow-actions-primary-button') + .locator('visible=true'), + ).toBeEnabled(); + + await page + .locator('#workflow-actions-primary-button') + .locator('visible=true') + .click(); + + await page + .getByTestId('workflow-reset-event-id-select-button') + .locator('visible=true') + .click(); + + await page + .locator('#reset-event-id-select') + .locator('[role="option"]') + .locator('visible=true') + .first() + .click(); + + await page + .getByTestId('reset-exclude-signals-checkbox') + .locator('visible=true') + .click(); + + await page + .getByTestId('reset-confirmation-modal') + .locator('visible=true') + .getByTestId('confirm-modal-button') + .click(); + + const request = await requestPromise; + const body = request.postDataJSON(); + + expect(body.resetReapplyExcludeTypes).toStrictEqual([ + ResetReapplyExcludeType.RESET_REAPPLY_EXCLUDE_TYPE_SIGNAL, + ]); + + expect(body.resetReapplyType).toBe(3); + }); + + test('Allows excluding Updates after the reset point', async ({ page }) => { + const requestPromise = page.waitForRequest(WORKFLOW_RESET_API); + + await expect( + page.locator('#workflow-actions-menu-button').locator('visible=true'), + ).toBeEnabled(); + + await expect( + page + .locator('#workflow-actions-primary-button') + .locator('visible=true'), + ).toBeEnabled(); + + await page + .locator('#workflow-actions-primary-button') + .locator('visible=true') + .click(); + + await page + .getByTestId('workflow-reset-event-id-select-button') + .locator('visible=true') + .click(); + await page + .locator('#reset-event-id-select') + .locator('[role="option"]') + .locator('visible=true') + .first() + .click(); + + await page + .getByTestId('reset-exclude-updates-checkbox') + .locator('visible=true') + .click(); + + await page + .getByTestId('reset-confirmation-modal') + .locator('visible=true') + .getByTestId('confirm-modal-button') + .click(); + + const request = await requestPromise; + const body = request.postDataJSON(); + + expect(body.resetReapplyExcludeTypes).toStrictEqual([ + ResetReapplyExcludeType.RESET_REAPPLY_EXCLUDE_TYPE_UPDATE, + ]); + + expect(body.resetReapplyType).toBe(3); + }); + }); + test.describe('Workflow Terminate', () => { + test.beforeEach(async ({ page }) => { + await mockWorkflowApis(page, mockCompletedWorkflow); + await mockEventHistoryApi(page, mockContinuedAsNewEventHistory); + await mockClusterApi(page, { serverVersion: '1.24.0' }); + await page.goto(workflowUrl); + }); + + test('Allows terminating latest run if next execution exists', async ({ + page, + }) => { + const requestPromise = page.waitForRequest(WORKFLOW_TERMINATE_API); + + await expect( + page.locator('#workflow-actions-menu-button').locator('visible=true'), + ).toBeEnabled(); + + await expect(page.getByText('Latest Execution')).toBeVisible(); + + await expect( + page + .locator('#workflow-actions-primary-button') + .locator('visible=true'), + ).toBeEnabled(); + + await page + .locator('#workflow-actions-menu-button') + .locator('visible=true') + .click(); + + await page + .getByTestId('terminate-button') + .locator('visible=true') + .click(); + + await page + .getByTestId('terminate-confirmation-modal') + .locator('visible=true') + .getByTestId('confirm-modal-button') + .click(); + + await requestPromise; + }); + + test('Does not allow terminating if no latest run exists', async ({ + page, + }) => { + await mockEventHistoryApi(page); + + await expect( + page.locator('#workflow-actions-menu-button').locator('visible=true'), + ).toBeEnabled(); + + await expect(page.getByText('Latest Execution')).toBeHidden(); + + await expect( + page + .locator('#workflow-actions-primary-button') + .locator('visible=true'), + ).toBeEnabled(); + + await page + .locator('#workflow-actions-menu-button') + .locator('visible=true') + .click(); + + await expect(page.getByTestId('terminate-button')).toBeHidden(); + }); + }); +}); + +test.describe('Workflow actions for a Running Workflow', () => { + test.beforeEach(async ({ page }) => { + const { + workflowExecutionInfo: { + execution: { workflowId, runId }, + }, + } = mockWorkflow; + + const workflowUrl = `/namespaces/default/workflows/${workflowId}/${runId}`; + + await page.goto(workflowUrl); + await mockWorkflowApis(page); + await mockWorkflowResetApi(page); + }); + + test('All actions are disabled when global write actions are disabled via Settings API', async ({ + page, + }) => { + await mockSettingsApi(page, { DisableWriteActions: true }); + + await expect( + page.locator('#workflow-actions-menu-button').locator('visible=true'), + ).toBeDisabled(); + + await expect( + page.locator('#workflow-actions-primary-button').locator('visible=true'), + ).toBeDisabled(); + }); + + test.describe('Workflow Cancel', () => { + test('is disabled when cancel is disabled via Settings API', async ({ + page, + }) => { + await mockSettingsApi(page, { WorkflowCancelDisabled: true }); + + await expect( + page + .locator('#workflow-actions-primary-button') + .locator('visible=true'), + ).toBeDisabled(); + + await page + .locator('#workflow-actions-menu-button') + .locator('visible=true') + .click(); + + await expect( + page + .locator('#workflow-actions-menu') + .locator('visible=true') + .getByText('Send a Signal'), + ).toBeEnabled(); + + await expect( + page + .locator('#workflow-actions-menu') + .locator('visible=true') + .getByText('Terminate'), + ).toBeEnabled(); + + await expect( + page + .locator('#workflow-actions-menu') + .locator('visible=true') + .getByText('Reset'), + ).toBeEnabled(); + }); + }); + + test.describe('Workflow Terminate', () => { + test('is disabled when teminate is disabled via Settings API', async ({ + page, + }) => { + await mockSettingsApi(page, { WorkflowTerminateDisabled: true }); + + await expect( + page + .locator('#workflow-actions-primary-button') + .locator('visible=true'), + ).toBeEnabled(); + + await page + .locator('#workflow-actions-menu-button') + .locator('visible=true') + .click(); + + await expect( + page + .locator('#workflow-actions-menu') + .locator('visible=true') + .getByText('Send a Signal'), + ).toBeEnabled(); + + await expect( + page + .locator('#workflow-actions-menu') + .locator('visible=true') + .getByText('Terminate'), + ).toBeDisabled(); + + await expect( + page + .locator('#workflow-actions-menu') + .locator('visible=true') + .getByText('Reset'), + ).toBeEnabled(); + }); + }); + + test.describe('Workflow Signal', () => { + test('is disabled when signal is disabled via Settings API', async ({ + page, + }) => { + await mockSettingsApi(page, { WorkflowSignalDisabled: true }); + + await expect( + page + .locator('#workflow-actions-primary-button') + .locator('visible=true'), + ).toBeEnabled(); + + await page + .locator('#workflow-actions-menu-button') + .locator('visible=true') + .click(); + + await expect( + page + .locator('#workflow-actions-menu') + .locator('visible=true') + .getByText('Send a Signal'), + ).toBeDisabled(); + + await expect( + page + .locator('#workflow-actions-menu') + .locator('visible=true') + .getByText('Terminate'), + ).toBeEnabled(); + + await expect( + page + .locator('#workflow-actions-menu') + .locator('visible=true') + .getByText('Reset'), + ).toBeEnabled(); + }); + }); +}); diff --git a/tests/integration/workflow-bulk-actions.spec.ts b/tests/integration/workflow-bulk-actions.spec.ts new file mode 100644 index 000000000..83dbdcc59 --- /dev/null +++ b/tests/integration/workflow-bulk-actions.spec.ts @@ -0,0 +1,142 @@ +import { expect, test } from '@playwright/test'; + +import { + mockBatchOperationApis, + mockClusterApi, + mockWorkflowsApis, + waitForWorkflowsApis, +} from '~/test-utilities/mock-apis'; + +test.describe('Batch and Bulk Workflow Actions', () => { + test.describe('when advanced visibility is enabled', () => { + test.beforeEach(async ({ page }) => { + await mockWorkflowsApis(page); + await mockClusterApi(page, { + visibilityStore: 'elasticsearch', + persistenceStore: 'postgres,elasticsearch', + }); + await mockBatchOperationApis(page); + + await page.goto('/namespaces/default/workflows'); + + await waitForWorkflowsApis(page); + await page.waitForSelector('[data-testid="workflow-count"]'); + }); + + test('allows running workflows to be terminated by ID without a reason', async ({ + page, + }) => { + await page.getByTestId('batch-actions-checkbox').click(); + await page.click('[data-testid="bulk-terminate-button"]'); + await page + .getByTestId('batch-terminate-confirmation') + .getByTestId('confirm-modal-button') + .click(); + await expect( + page.locator('#batch-terminate-success-toast'), + ).toBeVisible(); + }); + + test('allows running workflows to be terminated by a query', async ({ + page, + }) => { + await page.locator('#search-attribute-filter-button').click(); + await page.getByTestId('manual-search-toggle').click(); + await page.locator('#manual-search').fill('ExecutionStatus="Running"'); + await page.getByTestId('manual-search-button').click(); + await page.getByTestId('batch-actions-checkbox').click(); + await page.click('[data-testid="select-all-workflows"]'); + await page.click('[data-testid="bulk-terminate-button"]'); + const batchActionWorkflowsQuery = page + .getByTestId('batch-terminate-confirmation') + .getByTestId('batch-action-workflows-query'); + await expect(batchActionWorkflowsQuery).toHaveText( + 'ExecutionStatus="Running"', + ); + await page.fill( + '[data-testid="batch-terminate-confirmation"] #bulk-action-reason-2', + 'Sarah Connor', + ); + await page + .getByTestId('batch-terminate-confirmation') + .getByTestId('confirm-modal-button') + .click(); + await expect( + page.locator('#batch-terminate-success-toast'), + ).toBeVisible(); + }); + + test('allows running workflows to be cancelled by ID without a reason', async ({ + page, + }) => { + await page.getByTestId('batch-actions-checkbox').click(); + await page.click('[data-testid="bulk-cancel-button"]'); + await page + .getByTestId('batch-cancel-confirmation') + .getByTestId('confirm-modal-button') + .click(); + await expect(page.locator('#batch-cancel-success-toast')).toBeVisible(); + }); + + test('allows running workflows to be cancelled by a query', async ({ + page, + }) => { + await page.locator('#search-attribute-filter-button').click(); + await page.getByTestId('manual-search-toggle').click(); + await page.locator('#manual-search').fill('ExecutionStatus="Running"'); + await page.getByTestId('manual-search-button').click(); + await page.getByTestId('batch-actions-checkbox').click(); + await page.click('[data-testid="select-all-workflows"]'); + await page.click('[data-testid="bulk-cancel-button"]'); + const batchActionWorkflowsQuery = page + .getByTestId('batch-cancel-confirmation') + .getByTestId('batch-action-workflows-query'); + await expect(batchActionWorkflowsQuery).toHaveText( + 'ExecutionStatus="Running"', + ); + await page.fill( + '[data-testid="batch-cancel-confirmation"] #bulk-action-reason-0', + 'Sarah Connor', + ); + await page + .getByTestId('batch-cancel-confirmation') + .getByTestId('confirm-modal-button') + .click(); + await expect(page.locator('#batch-cancel-success-toast')).toBeVisible(); + }); + + test('works when visiting a URL directly that has an existing query in it', async ({ + page, + }) => { + await page.goto( + '/namespaces/default/workflows?query=WorkflowId%3D"test"', + ); + + await waitForWorkflowsApis(page); + + await page.getByTestId('batch-actions-checkbox').click(); + await page.getByTestId('select-all-workflows').click(); + await page.getByTestId('bulk-cancel-button').click(); + + const cancelQueryValue = await page + .getByTestId('batch-cancel-confirmation') + .getByTestId('batch-action-workflows-query') + .innerText(); + + expect(cancelQueryValue).toBe('WorkflowId="test"'); + + await page + .getByTestId('batch-cancel-confirmation') + .getByLabel('Cancel') + .click(); + await page.getByTestId('bulk-terminate-button').click(); + + const terminateQueryValue = await page + .getByTestId('batch-terminate-confirmation') + .getByTestId('batch-action-workflows-query') + .innerText(); + + expect(terminateQueryValue).toBe('WorkflowId="test"'); + }); + }); +}); diff --git a/tests/integration/workflow-event-history.spec.ts b/tests/integration/workflow-event-history.spec.ts new file mode 100644 index 000000000..6c6e113be --- /dev/null +++ b/tests/integration/workflow-event-history.spec.ts @@ -0,0 +1,89 @@ +import { expect, test } from '@playwright/test'; + +import { mockWorkflowApis } from '~/test-utilities/mock-apis'; +import { mockWorkflow } from '~/test-utilities/mocks/workflow'; + +const workflowUrl = `/namespaces/default/workflows/${mockWorkflow.workflowExecutionInfo.execution.workflowId}/${mockWorkflow.workflowExecutionInfo.execution.runId}/history`; + +test.describe('Workflow History', () => { + test.beforeEach(async ({ page }) => { + await page.goto(workflowUrl); + }); + + test('Workflow Execution shows WorkflowId and all sections and event history', async ({ + page, + }) => { + await mockWorkflowApis(page); + await expect(page.getByTestId('workflow-id-heading')).toHaveText( + '09db15_Running Click to copy content', + ); + await expect(page.getByTestId('history-tab')).toBeVisible(); + await expect(page.getByTestId('workers-tab')).toBeVisible(); + await expect(page.getByTestId('relationships-tab')).toBeVisible(); + await expect(page.getByTestId('pending-activities-tab')).toBeVisible(); + await expect(page.getByTestId('call-stack-tab')).toBeVisible(); + await expect(page.getByTestId('queries-tab')).toBeVisible(); + await expect(page.getByTestId('input-and-result')).toBeVisible(); + await expect(page.getByTestId('feed')).toBeVisible(); + await expect(page.getByTestId('compact')).toBeVisible(); + await expect(page.getByTestId('json')).toBeVisible(); + await expect(page.getByTestId('event-summary-table')).toBeVisible(); + + const firstRow = page.getByTestId('event-summary-row').first(); + await firstRow.click(); + + const firstRowId = firstRow.getByTestId('link'); + await firstRowId.click(); + + await mockWorkflowApis(page); + + await expect(page.getByTestId('workflow-id-heading')).toHaveText( + '09db15_Running Click to copy content', + ); + }); + + test('Workflow Execution links to specific event', async ({ page }) => { + await mockWorkflowApis(page); + await expect(page.getByTestId('workflow-id-heading')).toHaveText( + '09db15_Running Click to copy content', + ); + + const firstRow = page.getByTestId('event-summary-row').first(); + await firstRow.click(); + + const firstRowId = firstRow.getByTestId('link'); + await firstRowId.click(); + + await mockWorkflowApis(page); + + await expect(page.getByTestId('workflow-id-heading')).toHaveText( + '09db15_Running Click to copy content', + ); + + await expect(page.getByTestId('history-tab')).toBeVisible(); + await expect(page.getByTestId('workers-tab')).toBeVisible(); + await expect(page.getByTestId('relationships-tab')).toBeVisible(); + await expect(page.getByTestId('pending-activities-tab')).toBeVisible(); + await expect(page.getByTestId('call-stack-tab')).toBeVisible(); + await expect(page.getByTestId('queries-tab')).toBeVisible(); + await expect(page.getByTestId('input-and-result')).toBeHidden(); + await expect(page.getByTestId('feed')).toBeHidden(); + await expect(page.getByTestId('compact')).toBeHidden(); + await expect(page.getByTestId('json')).toBeHidden(); + await expect(page.getByTestId('event-summary-log')).toBeVisible(); + + await page.getByTestId('history-tab').click(); + + await expect(page.getByTestId('history-tab')).toBeVisible(); + await expect(page.getByTestId('workers-tab')).toBeVisible(); + await expect(page.getByTestId('relationships-tab')).toBeVisible(); + await expect(page.getByTestId('pending-activities-tab')).toBeVisible(); + await expect(page.getByTestId('call-stack-tab')).toBeVisible(); + await expect(page.getByTestId('queries-tab')).toBeVisible(); + await expect(page.getByTestId('input-and-result')).toBeVisible(); + await expect(page.getByTestId('feed')).toBeVisible(); + await expect(page.getByTestId('compact')).toBeVisible(); + await expect(page.getByTestId('json')).toBeVisible(); + await expect(page.getByTestId('event-summary-table')).toBeVisible(); + }); +}); diff --git a/tests/integration/workflow-navigation.desktop.spec.ts b/tests/integration/workflow-navigation.desktop.spec.ts new file mode 100644 index 000000000..efb9d46a2 --- /dev/null +++ b/tests/integration/workflow-navigation.desktop.spec.ts @@ -0,0 +1,82 @@ +import { expect, test } from '@playwright/test'; + +import { mockWorkflowsApis } from '~/test-utilities/mock-apis'; + +test.beforeEach(async ({ page, baseURL }) => { + await mockWorkflowsApis(page); + await page.goto(baseURL, { waitUntil: 'domcontentloaded' }); +}); + +test('Top Navigation current namespace is present', async ({ page }) => { + const switcher = page + .getByTestId('namespace-switcher') + .locator('visible=true'); + await expect(switcher).toBeVisible(); + await switcher.click(); + await expect(switcher).toBeFocused(); + await expect(page.getByRole('option', { name: 'default' })).toBeVisible(); +}); + +test('Top Navigation current namespace is present and has other namespaces to search and navigate to', async ({ + page, +}) => { + const switcher = page + .getByTestId('namespace-switcher') + .locator('visible=true'); + await expect(switcher).toBeVisible(); + await switcher.click(); + await expect(switcher).toBeFocused(); + await expect(page.getByRole('option', { name: 'default' })).toBeVisible(); + await expect( + page.getByRole('option', { name: 'some-other-namespace' }), + ).toBeVisible(); + await expect( + page.getByRole('option', { name: 'temporal-system' }), + ).toBeHidden(); + + await switcher.clear(); + await switcher.fill('some'); + await expect(page.getByRole('option', { name: 'default' })).toBeHidden(); + + await page.getByRole('option', { name: 'some-other-namespace' }).click(); + await expect(switcher).toHaveValue('some-other-namespace'); +}); + +test('Top Navigation current namespace is present and has other namespaces to search and stays open with space press', async ({ + page, +}) => { + const switcher = page + .getByTestId('namespace-switcher') + .locator('visible=true'); + await expect(switcher).toBeVisible(); + await switcher.click(); + await expect(switcher).toBeFocused(); + await expect(page.getByRole('option', { name: 'default' })).toBeVisible(); + await expect( + page.getByRole('option', { name: 'some-other-namespace' }), + ).toBeVisible(); + await expect( + page.getByRole('option', { name: 'temporal-system' }), + ).toBeHidden(); + + await switcher.clear(); + await switcher.type('some other namespace'); + await expect(page.getByRole('option', { name: 'default' })).toBeHidden(); + await expect( + page.getByRole('option', { name: 'some-other-namespace' }), + ).toBeHidden(); +}); + +test('Top Navigation current namespace is NOT present on non-namespace specific pages', async ({ + page, +}) => { + const switcher = page + .getByTestId('namespace-switcher') + .locator('visible=true'); + + await expect(switcher).toBeVisible(); + await page.getByRole('link', { name: 'Namespaces' }).click(); + await expect(switcher).toBeHidden(); + await page.getByRole('link', { name: 'Workflows' }).first().click(); + await expect(switcher).toBeVisible(); +}); diff --git a/tests/integration/workflow-navigation.mobile.spec.ts b/tests/integration/workflow-navigation.mobile.spec.ts new file mode 100644 index 000000000..59cf59f30 --- /dev/null +++ b/tests/integration/workflow-navigation.mobile.spec.ts @@ -0,0 +1,110 @@ +import { expect, test } from '@playwright/test'; + +import { mockWorkflowsApis } from '~/test-utilities/mock-apis'; + +test.beforeEach(async ({ page, baseURL }) => { + await page.goto(baseURL); + await mockWorkflowsApis(page); +}); + +test('Top Navigation current namespace is present', async ({ page }) => { + const switcher = page + .getByTestId('namespace-switcher') + .locator('visible=true'); + await expect(switcher).toBeVisible(); + await expect(switcher).toContainText(/default/i); +}); + +test('Top Navigation current namespace is present and has other namespaces to search and navigate to', async ({ + page, +}) => { + const switcher = page + .getByTestId('namespace-switcher') + .locator('visible=true'); + await expect(switcher).toBeVisible(); + await switcher.click(); + const searchInput = page.locator('#namespace-search'); + await expect( + page + .getByTestId('namespace-search-list') + .getByRole('button', { name: 'default' }), + ).toBeVisible(); + await expect( + page + .getByTestId('namespace-search-list') + .getByRole('button', { name: 'some-other-namespace' }), + ).toBeVisible(); + await expect( + page + .getByTestId('namespace-search-list') + .getByRole('button', { name: 'temporal-system' }), + ).toBeHidden(); + + await searchInput.clear(); + await searchInput.fill('some'); + await expect( + page + .getByTestId('namespace-search-list') + .getByRole('button', { name: 'default' }), + ).toBeHidden(); + + await page + .getByTestId('namespace-search-list') + .getByRole('button', { name: 'some-other-namespace' }) + .click(); + await expect(switcher).toContainText(/some/i); +}); + +test('Top Navigation current namespace is present and has other namespaces to search and stays open with space press', async ({ + page, +}) => { + const switcher = page + .getByTestId('namespace-switcher') + .locator('visible=true'); + await expect(switcher).toBeVisible(); + await switcher.click(); + const searchInput = page.locator('#namespace-search'); + await expect( + page + .getByTestId('namespace-search-list') + .getByRole('button', { name: 'default' }), + ).toBeVisible(); + await expect( + page + .getByTestId('namespace-search-list') + .getByRole('button', { name: 'some-other-namespace' }), + ).toBeVisible(); + await expect( + page + .getByTestId('namespace-search-list') + .getByRole('button', { name: 'temporal-system' }), + ).toBeHidden(); + + await searchInput.clear(); + await searchInput.fill('some other namespace'); + await expect( + page + .getByTestId('namespace-search-list') + .getByRole('button', { name: 'default' }), + ).toBeHidden(); + await expect( + page + .getByTestId('namespace-search-list') + .getByRole('button', { name: 'some-other-namespace' }), + ).toBeHidden(); +}); + +test('Top Navigation current namespace is NOT present on non-namespace specific pages', async ({ + page, +}) => { + const switcher = page + .getByTestId('namespace-switcher') + .locator('visible=true'); + await expect(switcher).toBeVisible(); + await page.getByTestId('nav-menu-button').click(); + await page.getByRole('link', { name: 'Namespaces' }).click(); + await expect(switcher).toBeHidden(); + await page.getByTestId('nav-menu-button').click(); + await page.getByRole('link', { name: 'Workflows' }).click(); + await expect(switcher).toBeVisible(); +}); diff --git a/tests/integration/workflow-reset.spec.ts b/tests/integration/workflow-reset.spec.ts new file mode 100644 index 000000000..54c6f9f37 --- /dev/null +++ b/tests/integration/workflow-reset.spec.ts @@ -0,0 +1,45 @@ +import { expect, test } from '@playwright/test'; + +import { mockWorkflowApis } from '~/test-utilities/mock-apis'; +import { + mockCompletedWorkflow, + mockResetWorkflow, +} from '~/test-utilities/mocks/workflow'; + +test.describe('Workflow Reset Modal Shown', () => { + const { + workflowExecutionInfo: { + execution: { workflowId, runId }, + }, + } = mockCompletedWorkflow; + const workflowUrl = `/namespaces/default/workflows/${workflowId}/${runId}`; + test.beforeEach(async ({ page }) => { + await mockWorkflowApis(page, mockResetWorkflow); + }); + test('shows reset alert if workflow was reset', async ({ page }) => { + await page.goto(workflowUrl); + + const resetAlert = page.getByTestId('workflow-reset-alert'); + await expect(resetAlert).toBeVisible(); + }); +}); + +test.describe('Workflow Reset Modal Not Shown', () => { + const { + workflowExecutionInfo: { + execution: { workflowId, runId }, + }, + } = mockCompletedWorkflow; + const workflowUrl = `/namespaces/default/workflows/${workflowId}/${runId}`; + test.beforeEach(async ({ page }) => { + await mockWorkflowApis(page, mockCompletedWorkflow); + }); + test('does not show reset alert if workflow was not reset', async ({ + page, + }) => { + await page.goto(workflowUrl); + + const resetAlert = page.getByTestId('workflow-reset-alert'); + await expect(resetAlert).toHaveCount(0); + }); +}); diff --git a/tests/integration/workflows-count.spec.ts b/tests/integration/workflows-count.spec.ts new file mode 100644 index 000000000..922dc81ae --- /dev/null +++ b/tests/integration/workflows-count.spec.ts @@ -0,0 +1,36 @@ +import { expect, test } from '@playwright/test'; + +import { + mockClusterApi, + mockWorkflowsApis, + mockWorkflowsGroupByCountApi, + waitForWorkflowsApis, +} from '~/test-utilities/mock-apis'; + +test.describe('Workflows List with Counts', () => { + test.beforeEach(async ({ page }) => { + await mockWorkflowsApis(page); + await mockClusterApi(page, { + visibilityStore: 'elasticsearch', + persistenceStore: 'postgres,elasticsearch', + }); + await mockWorkflowsGroupByCountApi(page); + + page.goto('/namespaces/default/workflows'); + + await waitForWorkflowsApis(page); + }); + + test.describe('Shows only result count', () => { + test('Counts of workflows ', async ({ page }) => { + await page.waitForSelector('[data-testid="workflow-count"]'); + await expect(page.getByTestId('workflow-count')).toHaveText('31,230'); + await expect(page.getByTestId('workflow-status-Running')).toHaveText( + '6 Running', + ); + await expect(page.getByTestId('workflow-status-Completed')).toHaveText( + '21,652 Completed', + ); + }); + }); +}); diff --git a/tests/integration/workflows-search-attribute-filter.desktop.spec.ts b/tests/integration/workflows-search-attribute-filter.desktop.spec.ts new file mode 100644 index 000000000..146a150ab --- /dev/null +++ b/tests/integration/workflows-search-attribute-filter.desktop.spec.ts @@ -0,0 +1,60 @@ +import { expect, test } from '@playwright/test'; + +import { + mockClusterApi, + mockWorkflowApis, + mockWorkflowsApis, + waitForWorkflowsApis, +} from '~/test-utilities/mock-apis'; + +test.beforeEach(async ({ page }) => { + await mockWorkflowsApis(page); + await mockWorkflowApis(page); + + await mockClusterApi(page, { + visibilityStore: 'elasticsearch', + persistenceStore: 'postgres,elasticsearch', + }); + + page.goto('/namespaces/default/workflows'); + + await waitForWorkflowsApis(page); +}); + +const getDatetime = (query: string) => + query.split('=')[1].replace(/['"']+/g, ''); +const validDatetime = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3})Z$/; + +test('it should update the datetime filter based on the selected timezone', async ({ + page, +}) => { + await page.getByTestId('timezones-menu-button').click(); + await page.getByTestId('top-nav').getByPlaceholder('Search').fill('PDT'); + await page.getByText('Pacific Daylight Time (PDT) UTC-07:00').click(); + + await page.getByRole('button', { name: 'Filter' }).click(); + await page.getByText('CloseTime').click(); + await page.getByRole('menuitem', { name: 'After' }).click(); + await page.getByLabel('Absolute', { exact: true }).check(); + await page.getByLabel('hrs', { exact: true }).fill('5'); + await page.getByRole('button', { name: 'Apply' }).click(); + + await page.locator('#search-attribute-filter-button').click(); + await page.getByTestId('manual-search-toggle').click(); + + let filter = await page.getByTestId('CloseTime-0').innerText(); + expect(filter).toContain('05:00 AM'); + + let query = await page.locator('#manual-search').inputValue(); + expect(getDatetime(query)).toMatch(validDatetime); + + await page.getByTestId('timezones-menu-button').click(); + await page.getByTestId('top-nav').getByPlaceholder('Search').fill('MDT'); + await page.getByText('Mountain Daylight Time (MDT) UTC-06:00').click(); + + filter = await page.getByTestId('CloseTime-0').innerText(); + expect(filter).toContain('06:00 AM'); + + query = await page.locator('#manual-search').inputValue(); + expect(getDatetime(query)).toMatch(validDatetime); +}); diff --git a/tests/integration/workflows-search-attribute-filter.mobile.spec.ts b/tests/integration/workflows-search-attribute-filter.mobile.spec.ts new file mode 100644 index 000000000..11a10ec98 --- /dev/null +++ b/tests/integration/workflows-search-attribute-filter.mobile.spec.ts @@ -0,0 +1,88 @@ +import { expect, test } from '@playwright/test'; + +import { + mockClusterApi, + mockWorkflowApis, + mockWorkflowsApis, + waitForWorkflowsApis, +} from '~/test-utilities/mock-apis'; + +test.beforeEach(async ({ page }) => { + await mockWorkflowsApis(page); + await mockWorkflowApis(page); + + await mockClusterApi(page, { + visibilityStore: 'elasticsearch', + persistenceStore: 'postgres,elasticsearch', + }); + + page.goto('/namespaces/default/workflows'); + + await waitForWorkflowsApis(page); +}); + +const getDatetime = (query: string) => + query.split('=')[1].replace(/['"']+/g, ''); +const validDatetime = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3})Z$/; + +test('it should update the datetime filter based on the selected timezone', async ({ + page, +}) => { + await page.getByTestId('nav-profile-button').click(); + + await page + .getByTestId('timezones-menu-button') + .locator('visible=true') + .click(); + await page + .locator('#timezones-menu') + .locator('visible=true') + .getByPlaceholder('Search') + .fill('PDT'); + await page + .getByText('Pacific Daylight Time (PDT) UTC-07:00') + .locator('visible=true') + .click(); + + await page.getByTestId('nav-profile-button').click(); + + await page.getByRole('button', { name: 'Filter' }).click(); + await page.getByText('CloseTime').click(); + await page.getByRole('menuitem', { name: 'After' }).click(); + await page.getByLabel('Absolute', { exact: true }).check(); + await page.getByLabel('hrs', { exact: true }).fill('5'); + await page.getByRole('button', { name: 'Apply' }).click(); + + await page.locator('#search-attribute-filter-button').click(); + await page.getByTestId('manual-search-toggle').click(); + + let filter = await page.getByTestId('CloseTime-0').innerText(); + expect(filter).toContain('05:00 AM'); + + let query = await page.locator('#manual-search').inputValue(); + expect(getDatetime(query)).toMatch(validDatetime); + + await page.getByTestId('nav-profile-button').click(); + + await page + .getByTestId('timezones-menu-button') + .locator('visible=true') + .click(); + await page + .locator('#timezones-menu') + .locator('visible=true') + .getByPlaceholder('Search') + .fill('MDT'); + await page + .getByText('Mountain Daylight Time (MDT) UTC-06:00') + .locator('visible=true') + .click(); + + await page.getByTestId('nav-profile-button').click(); + + filter = await page.getByTestId('CloseTime-0').innerText(); + expect(filter).toContain('06:00 AM'); + + query = await page.locator('#manual-search').inputValue(); + expect(getDatetime(query)).toMatch(validDatetime); +}); diff --git a/tests/test-utilities/accessibility-reporter/index.ts b/tests/test-utilities/accessibility-reporter/index.ts new file mode 100644 index 000000000..e7f9cae72 --- /dev/null +++ b/tests/test-utilities/accessibility-reporter/index.ts @@ -0,0 +1,80 @@ +import { writeFile } from 'fs/promises'; +import { dirname, join } from 'path'; + +import type { Reporter, TestCase, TestResult } from '@playwright/test/reporter'; +import { Result } from 'axe-core'; +import { chalk } from 'zx'; + +import { type AccessibilityViolationSummary, summarize } from './summarize'; +import { toMarkdown } from './to-markdown'; +import { unique } from './unique'; + +class AccessibilityReporter implements Reporter { + public violations: AccessibilityViolationSummary[] = []; + public options: { outputFile: string } = { + outputFile: './playwright-report/accessibility-violations.json', + }; + + constructor( + options = { + outputFile: './playwright-report/accessibility-violations.json', + }, + ) { + this.options = options; + + console.log('🦾 Collecting accessibility violations…'); + console.log( + `📝 Violations will be written to: ${chalk.cyan( + this.options.outputFile, + )}`, + ); + } + + onTestEnd(_: TestCase, result: TestResult) { + if (result.status === 'passed') return; + if (!result.attachments) return; + + const violationAttachment = result.attachments.find( + (attachemnt) => attachemnt.name === 'violations', + ); + + if (!violationAttachment) return; + + const { violations, url } = JSON.parse(violationAttachment.body.toString()); + + this.violations.push( + ...violations.map((violation: Result) => summarize(violation, url)), + ); + } + + async onEnd() { + if (!this.violations.length) return; + + const violations = unique(this.violations); + + await writeFile( + this.options.outputFile, + JSON.stringify(violations, null, 2), + ).catch((error) => console.error(error)); + + const markdown = await toMarkdown(violations); + const markdownFile = join( + dirname(this.options.outputFile) + '/accessibility-violations.md', + ); + + await writeFile(markdownFile, markdown).catch((error) => + console.error(error), + ); + + console.log( + `🐱 ${chalk.red( + violations.length, + )} accessibility violations written to: ${chalk.cyan( + this.options.outputFile, + )}`, + ); + + console.log(`\t↳ Markdown report written to: ${chalk.cyan(markdownFile)}`); + } +} +export default AccessibilityReporter; diff --git a/tests/test-utilities/accessibility-reporter/summarize.ts b/tests/test-utilities/accessibility-reporter/summarize.ts new file mode 100644 index 000000000..2c2258b5f --- /dev/null +++ b/tests/test-utilities/accessibility-reporter/summarize.ts @@ -0,0 +1,43 @@ +import { createHash } from 'crypto'; + +import { NodeResult, Result } from 'axe-core'; + +import { WithScreenshot } from '../attach-violations'; + +export type AccessibilityViolationSummary = ReturnType; + +export const summarize = (issue: Result, url?: string) => { + const md5 = createHash('md5'); + const pathName = new URL(url || '').pathname; + + const nodes = issue.nodes.flatMap((node: WithScreenshot) => { + const { html, target, ancestry, failureSummary, element, screenshot } = + node; + + return { + html, + target, + ancestry, + screenshot, + failureSummary, + element, + url: pathName, + }; + }); + + const elements = nodes.reduce((acc, node) => { + return (acc += node.html); + }, ''); + + return { + id: md5.update(elements).digest('hex'), + issueType: issue.id, + url: pathName, + help: issue.help, + helpUrl: issue.helpUrl, + impact: issue.impact, + tags: issue.tags, + description: issue.description, + nodes: nodes, + }; +}; diff --git a/tests/test-utilities/accessibility-reporter/to-markdown.ts b/tests/test-utilities/accessibility-reporter/to-markdown.ts new file mode 100644 index 000000000..10fee6c5d --- /dev/null +++ b/tests/test-utilities/accessibility-reporter/to-markdown.ts @@ -0,0 +1,65 @@ +import { remark } from 'remark'; +import remarkGfm from 'remark-gfm'; +import remarkParse from 'remark-parse'; +import stringify from 'remark-stringify'; +import toc from 'remark-toc'; +import { visit } from 'unist-util-visit'; + +import { AccessibilityViolationSummary } from './summarize'; + +type Node = { + value: string; + type: string; +}; + +function escapeHTML() { + return (tree: Node) => { + visit(tree, 'html', (node: Node) => { + node.type = 'text'; + node.value = `\`${node.value}\``; // Wrap in backticks + }); + }; +} + +const processor = remark() + .use(remarkParse) + .use(escapeHTML) + .use(toc) + .use(remarkGfm) + .use(stringify); + +const renderNode = (node: AccessibilityViolationSummary['nodes'][number]) => { + const [fix, ...summary] = node.failureSummary.split('\n'); + + return [ + `\`${node.target.join(' ')}\`\n\n`, + // `![Screenshot](data:image/png;base64,${node.screenshot})\n\n`, + `${fix}\n\n`, + summary.map((line) => ` - ${line}`).join('\n'), + `\n\nThis is the markup in question:\n\n \`\`\`html\n${node.html}\n\`\`\`\n`, + ].join(''); +}; + +export const toMarkdown = async ( + data: AccessibilityViolationSummary | AccessibilityViolationSummary[], +): Promise => { + if (Array.isArray(data)) { + const combined = await Promise.all( + data.map(async (d) => await toMarkdown(d)), + ); + + const content = combined.join('\n\n---\n\n'); + + const html = await processor.process(content); + + return html.toString(); + } else { + return [ + `# Accessibility Issue: (${data.impact}) \`${data.url}\` — ${data.issueType}`, + `**Issue**: [${data.help}](${data.helpUrl})`, + `**Why?**: ${data.description}`, + `**Where?**: ${data.url}`, + data.nodes.map(renderNode).join('\n\n'), + ].join('\n\n'); + } +}; diff --git a/tests/test-utilities/accessibility-reporter/unique.ts b/tests/test-utilities/accessibility-reporter/unique.ts new file mode 100644 index 000000000..658e85210 --- /dev/null +++ b/tests/test-utilities/accessibility-reporter/unique.ts @@ -0,0 +1,9 @@ +export const unique = (items: T[]) => + items.reduce((acc: T[], current: T) => { + const x = acc.find((item) => item.id === current.id); + if (!x) { + return acc.concat([current]); + } else { + return acc; + } + }, []); diff --git a/tests/test-utilities/attach-violations.ts b/tests/test-utilities/attach-violations.ts new file mode 100644 index 000000000..fb83fdcc5 --- /dev/null +++ b/tests/test-utilities/attach-violations.ts @@ -0,0 +1,35 @@ +import type { Page, TestInfo } from '@playwright/test'; + +type AxeResults = import('axe-core').AxeResults; +export type WithScreenshot = T & { screenshot: string }; + +export const attachViolations = async ( + testInfo: TestInfo, + results: AxeResults, + page: Page, +) => { + for (const violation of results.violations) { + for (const node of violation.nodes) { + const element = await page.$(node.target.join(' ')); + + if (element) { + const screenshot = await element.screenshot(); + (node as WithScreenshot).screenshot = + screenshot.toString('base64'); + } + } + } + + return testInfo.attach('violations', { + body: JSON.stringify( + { + title: testInfo.title, + url: page.url(), + violations: results.violations, + }, + null, + 2, + ), + contentType: 'application/json', + }); +}; diff --git a/tests/test-utilities/custom-matchers.ts b/tests/test-utilities/custom-matchers.ts new file mode 100644 index 000000000..c922ecee7 --- /dev/null +++ b/tests/test-utilities/custom-matchers.ts @@ -0,0 +1,21 @@ +import { expect } from '@playwright/test'; + +expect.extend({ + async toHaveLocalStorageItem(page, key: string, expected: string) { + const stored = await page.evaluate((key) => localStorage.getItem(key), key); + const pass = stored === JSON.stringify(expected); + return { + pass, + message: () => + pass + ? `Expected local storage at key "${key}" not to have value "${JSON.stringify(expected)}"` + : `Expected local storage at key "${key}" to have value ${JSON.stringify(expected)}, but got ${stored}`, + }; + }, +}); + +declare module '@playwright/test' { + interface Matchers { + toHaveLocalStorageItem(key: string, expected: string): Promise; + } +} diff --git a/tests/test-utilities/mock-apis.ts b/tests/test-utilities/mock-apis.ts new file mode 100644 index 000000000..0a5e55be9 --- /dev/null +++ b/tests/test-utilities/mock-apis.ts @@ -0,0 +1,152 @@ +import type { Page } from '@playwright/test'; + +import { + mockCreateBatchOperationApi, + mockDescribeBatchOperationApi, +} from './mocks/batch-operations'; +import { CLUSTER_API, mockClusterApi } from './mocks/cluster'; +import { + EVENT_HISTORY_API, + EVENT_HISTORY_API_REVERSE, + mockEventHistoryApi, +} from './mocks/event-history'; +import { mockNamespaceApi } from './mocks/namespace'; +import { mockNamespacesApi, NAMESPACES_API } from './mocks/namespaces'; +import { mockSchedulesApi } from './mocks/schedules'; +import { mockSearchAttributesApi } from './mocks/search-attributes'; +import { mockSettingsApi, SETTINGS_API } from './mocks/settings'; +import { mockSystemInfoApi } from './mocks/system-info'; +import { mockTaskQueuesApi, TASK_QUEUES_API } from './mocks/task-queues'; +import { mockWorkflow, mockWorkflowApi, WORKFLOW_API } from './mocks/workflow'; +import { mockWorkflowsApi, WORKFLOWS_API } from './mocks/workflows'; +import { + mockWorkflowsCountApi, + WORKFLOWS_COUNT_API, +} from './mocks/workflows-count'; + +import { + SearchAttributesResponse, + WorkflowExecutionAPIResponse, +} from '$src/lib/types/workflows'; + +export { mockClusterApi, CLUSTER_API } from './mocks/cluster'; +export { mockNamespaceApi, NAMESPACE_API } from './mocks/namespace'; +export { mockNamespacesApi, NAMESPACES_API } from './mocks/namespaces'; +export { mockSettingsApi, SETTINGS_API } from './mocks/settings'; +export { mockSystemInfoApi } from './mocks/system-info'; + +export { + mockSearchAttributesApi, + SEARCH_ATTRIBUTES_API, +} from './mocks/search-attributes'; +export { mockScheduleApi, SCHEDULE_API } from './mocks/schedules'; +export { mockWorkflowsApi, WORKFLOWS_API } from './mocks/workflows'; +export { mockWorkflowApi, WORKFLOW_API } from './mocks/workflow'; +export { + mockWorkflowsCountApi, + mockWorkflowsGroupByCountApi, + WORKFLOWS_COUNT_API, +} from './mocks/workflows-count'; +export { + mockCreateBatchOperationApi, + mockDescribeBatchOperationApi, + CREATE_BATCH_OPERATION_API, + DESCRIBE_BATCH_OPERATION_API, +} from './mocks/batch-operations'; +export { EVENT_HISTORY_API, mockEventHistoryApi } from './mocks/event-history'; +export { mockTaskQueuesApi, TASK_QUEUES_API } from './mocks/task-queues'; + +export const mockGlobalApis = (page: Page) => { + return Promise.all([ + mockClusterApi(page), + mockNamespacesApi(page), + mockSettingsApi(page), + mockSystemInfoApi(page), + ]); +}; + +export const mockWorkflowsApis = (page: Page) => { + return Promise.all([ + mockGlobalApis(page), + mockWorkflowsApi(page), + mockSearchAttributesApi(page), + mockWorkflowsCountApi(page), + ]); +}; + +export const mockSchedulesApis = ( + page: Page, + empty = false, + emptyWorkflowsCount = false, + customSearchAttributes?: Partial, +) => { + return Promise.all([ + mockGlobalApis(page), + mockSearchAttributesApi(page, customSearchAttributes), + mockSchedulesApi(page, empty), + mockWorkflowsCountApi(page, emptyWorkflowsCount), + ]); +}; + +export const mockNamespaceApis = (page: Page) => { + return Promise.all([ + mockGlobalApis(page), + mockNamespaceApi(page), + mockSearchAttributesApi(page), + ]); +}; + +export const mockBatchOperationApis = (page: Page) => { + return Promise.all([ + mockCreateBatchOperationApi(page), + mockDescribeBatchOperationApi(page), + ]); +}; + +export const mockWorkflowApis = ( + page: Page, + workflow: WorkflowExecutionAPIResponse = mockWorkflow, +) => { + return Promise.all([ + mockNamespaceApis(page), + mockWorkflowApi(page, workflow), + mockEventHistoryApi(page), + mockTaskQueuesApi(page), + ]); +}; + +/** + * Waits for requests that occur on initial load of every page, i.e. cluster, settings, etc. + * @param page a playwright Page object + * @returns Promise + */ +export const waitForGlobalApis = (page: Page) => { + return Promise.all([ + page.waitForResponse(CLUSTER_API), + page.waitForResponse(NAMESPACES_API), + page.waitForResponse(SETTINGS_API), + ]); +}; + +/** + * Waits for requests that occur on navigation to the Workflows page, excluding global APIs + * @param page a playwright Page object + * @param waitForCount boolean - whether to wait for the Workflows Count Api - which only gets requested when advanced visibility in enabled + * @returns Promise + */ +export const waitForWorkflowsApis = (page: Page, waitForCount = true) => { + const requests = [page.waitForResponse(WORKFLOWS_API)]; + + if (waitForCount) requests.push(page.waitForResponse(WORKFLOWS_COUNT_API)); + + return Promise.all(requests); +}; + +export const waitForWorkflowApis = (page: Page) => { + return Promise.all([ + page.waitForResponse(WORKFLOW_API), + page.waitForResponse(EVENT_HISTORY_API), + page.waitForResponse(EVENT_HISTORY_API_REVERSE), + page.waitForResponse(TASK_QUEUES_API), + ]); +}; diff --git a/tests/test-utilities/mock-date.ts b/tests/test-utilities/mock-date.ts new file mode 100644 index 000000000..a63f776fa --- /dev/null +++ b/tests/test-utilities/mock-date.ts @@ -0,0 +1,17 @@ +export const mockDate = () => { + const _Date = window.Date; + + class FakeDate extends _Date { + constructor( + date: + | number + | string + | Date = 'Wed Sept 19 2012 12:00:00 GMT-0600 (Mountain Daylight Time)', + ) { + super(); + return new _Date(date); + } + } + + window.Date = FakeDate as DateConstructor; +}; diff --git a/tests/test-utilities/mock-local-storage.ts b/tests/test-utilities/mock-local-storage.ts new file mode 100644 index 000000000..d985f33b6 --- /dev/null +++ b/tests/test-utilities/mock-local-storage.ts @@ -0,0 +1,23 @@ +import type { Page } from '@playwright/test'; + +export const setLocalStorage = async ( + key: string, + value: string, + page: Page, +) => { + await page.addInitScript( + (item) => { + window.localStorage.setItem(item.key, item.value); + }, + { key, value }, + ); + await page.reload(); +}; + +export const removeLocalStorageItem = async ( + key: string, + page: Page, +): Promise => { + await page.addInitScript((key) => window.localStorage.removeItem(key), key); + await page.reload(); +}; diff --git a/tests/test-utilities/mocks/batch-operations.ts b/tests/test-utilities/mocks/batch-operations.ts new file mode 100644 index 000000000..9e648c81d --- /dev/null +++ b/tests/test-utilities/mocks/batch-operations.ts @@ -0,0 +1,19 @@ +import { Page } from '@playwright/test'; + +export const CREATE_BATCH_OPERATION_API = + '**/api/v1/namespaces/*/batch-operations*'; + +export const DESCRIBE_BATCH_OPERATION_API = + '**/api/v1/namespaces/*/batch-operations/*'; + +export const mockCreateBatchOperationApi = (page: Page) => { + return page.route(CREATE_BATCH_OPERATION_API, (route) => { + route.fulfill({ json: {} }); + }); +}; + +export const mockDescribeBatchOperationApi = (page: Page) => { + return page.route(DESCRIBE_BATCH_OPERATION_API, (route) => { + route.fulfill({ json: { state: 'COMPLETED', completeOperationCount: 10 } }); + }); +}; diff --git a/tests/test-utilities/mocks/cluster.ts b/tests/test-utilities/mocks/cluster.ts new file mode 100644 index 000000000..3c366ac4c --- /dev/null +++ b/tests/test-utilities/mocks/cluster.ts @@ -0,0 +1,40 @@ +import type { Page } from '@playwright/test'; + +import type { GetClusterInfoResponse as Cluster } from '$src/lib/types'; + +export const CLUSTER_API = '**/api/v1/cluster-info?'; + +const MOCK_CLUSTER: Cluster = { + supportedClients: { + 'temporal-cli': '\u003c2.0.0', + 'temporal-go': '\u003c2.0.0', + 'temporal-java': '\u003c2.0.0', + 'temporal-php': '\u003c2.0.0', + 'temporal-server': '\u003c2.0.0', + 'temporal-typescript': '\u003c2.0.0', + 'temporal-ui': '\u003c3.0.0', + }, + serverVersion: '1.22.0', + clusterId: 'f2f23e30-2294-4bf8-a5ec-b505259f30c9', + versionInfo: null, + clusterName: 'active', + historyShardCount: 1, + persistenceStore: 'sqlite', + visibilityStore: 'sqlite', +}; + +export const mergeCluster = (cluster: Partial): Cluster => { + return { + ...MOCK_CLUSTER, + ...cluster, + }; +}; + +export const mockClusterApi = async ( + page: Page, + cluster?: Partial, +) => { + return page.route(CLUSTER_API, async (route) => { + route.fulfill({ json: mergeCluster(cluster) }); + }); +}; diff --git a/tests/test-utilities/mocks/event-history.ts b/tests/test-utilities/mocks/event-history.ts new file mode 100644 index 000000000..2a90f3eff --- /dev/null +++ b/tests/test-utilities/mocks/event-history.ts @@ -0,0 +1,463 @@ +import { Page } from '@playwright/test'; + +import { GetWorkflowExecutionHistoryResponse } from '$src/lib/types/events'; + +const mockEventHistory = { + history: { + events: [ + { + eventId: '1', + eventTime: '2022-04-28T05:30:19.427247101Z', + eventType: 'WorkflowExecutionStarted', + version: '0', + taskId: '1367516', + workflowExecutionStartedEventAttributes: { + workflowType: { + name: 'RainbowStatusesWorkflow', + }, + parentWorkflowNamespace: '', + parentWorkflowExecution: null, + parentInitiatedEventId: '0', + taskQueue: { + name: 'rainbow-statuses', + kind: 'Normal', + }, + input: { + payloads: [ + { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + }, + data: 'MQ==', + }, + ], + }, + workflowExecutionTimeout: '0s', + workflowRunTimeout: '0s', + workflowTaskTimeout: '10s', + continuedExecutionRunId: '', + initiator: 'Unspecified', + continuedFailure: null, + lastCompletionResult: null, + originalExecutionRunId: 'be1ded4f-3147-4ec7-8efb-30a447fef129', + identity: '168773@user0@', + firstExecutionRunId: 'be1ded4f-3147-4ec7-8efb-30a447fef129', + retryPolicy: null, + attempt: 1, + workflowExecutionExpirationTime: null, + cronSchedule: '', + firstWorkflowTaskBackoff: '0s', + memo: null, + searchAttributes: { + indexedFields: { + CustomBoolField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'Qm9vbA==', + }, + data: 'dHJ1ZQ==', + }, + CustomDatetimeField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'RGF0ZXRpbWU=', + }, + data: 'IjIwMjItMDQtMjhUMDU6MzA6MTkuNDI2MTkyMDg4WiI=', + }, + CustomDoubleField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'RG91Ymxl', + }, + data: 'MA==', + }, + CustomIntField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'SW50', + }, + data: 'MA==', + }, + CustomKeywordField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZA==', + }, + data: 'InJhaW5ib3ctc3RhdHVzZXMtZDQ5NGM0Ig==', + }, + CustomStringField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'VGV4dA==', + }, + data: 'InJhaW5ib3cgc3RhdHVzZXMgZDQ5NGM0IFJ1bm5pbmci', + }, + }, + }, + prevAutoResetPoints: null, + header: { + fields: {}, + }, + }, + }, + { + eventId: '2', + eventTime: '2022-04-28T05:30:19.427264889Z', + eventType: 'WorkflowTaskScheduled', + version: '0', + taskId: '1367517', + workflowTaskScheduledEventAttributes: { + taskQueue: { + name: 'rainbow-statuses', + kind: 'Normal', + }, + startToCloseTimeout: '10s', + attempt: 1, + }, + }, + { + eventId: '3', + eventTime: '2022-04-28T05:30:19.436957880Z', + eventType: 'WorkflowExecutionSignaled', + version: '0', + taskId: '1367521', + workflowExecutionSignaledEventAttributes: { + signalName: 'customSignal', + input: { + payloads: [ + { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + }, + data: 'eyJIZXkiOiJmcm9tIE1hcnMiLCJBdCI6IjIwMjItMDQtMjhUMDE6MzA6MTkuNDM0MjYyMzM1LTA0OjAwIn0=', + }, + ], + }, + identity: '168773@user0@', + header: { + fields: {}, + }, + }, + }, + { + eventId: '4', + eventTime: '2022-04-28T05:30:19.440848942Z', + eventType: 'WorkflowTaskStarted', + version: '0', + taskId: '1367523', + workflowTaskStartedEventAttributes: { + scheduledEventId: '2', + identity: '168631@user0@', + requestId: 'c2a89d60-2c1b-4a8f-b312-f11385722e7e', + }, + }, + { + eventId: '5', + eventTime: '2022-04-28T05:30:19.445592990Z', + eventType: 'WorkflowTaskCompleted', + version: '0', + taskId: '1367529', + workflowTaskCompletedEventAttributes: { + scheduledEventId: '2', + startedEventId: '4', + identity: '168631@user0@', + binaryChecksum: '04f0fb34cfd90d692fa1d506c626a598', + }, + }, + { + eventId: '6', + eventTime: '2022-04-28T05:30:19.445631589Z', + eventType: 'ActivityTaskScheduled', + version: '0', + taskId: '1367530', + activityTaskScheduledEventAttributes: { + activityId: '6', + activityType: { + name: 'LongActivity', + }, + namespace: '', + taskQueue: { + name: 'rainbow-statuses', + kind: 'Normal', + }, + header: { + fields: {}, + }, + input: null, + scheduleToCloseTimeout: '0s', + scheduleToStartTimeout: '0s', + startToCloseTimeout: '3600s', + heartbeatTimeout: '0s', + workflowTaskCompletedEventId: '5', + retryPolicy: { + initialInterval: '1s', + backoffCoefficient: 2, + maximumInterval: '100s', + maximumAttempts: 1, + nonRetryableErrorTypes: [], + }, + }, + }, + ], + }, + rawHistory: [], + nextPageToken: null, + archived: false, +}; + +export const mockContinuedAsNewEventHistory = { + history: { + events: [ + { + eventId: '1', + eventTime: '2022-04-28T05:30:19.427247101Z', + eventType: 'WorkflowExecutionStarted', + version: '0', + taskId: '1367516', + workflowExecutionStartedEventAttributes: { + workflowType: { + name: 'RainbowStatusesWorkflow', + }, + parentWorkflowNamespace: '', + parentWorkflowExecution: null, + parentInitiatedEventId: '0', + taskQueue: { + name: 'rainbow-statuses', + kind: 'Normal', + }, + input: { + payloads: [ + { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + }, + data: 'MQ==', + }, + ], + }, + workflowExecutionTimeout: '0s', + workflowRunTimeout: '0s', + workflowTaskTimeout: '10s', + continuedExecutionRunId: '', + initiator: 'Unspecified', + continuedFailure: null, + lastCompletionResult: null, + originalExecutionRunId: 'be1ded4f-3147-4ec7-8efb-30a447fef129', + identity: '168773@user0@', + firstExecutionRunId: 'be1ded4f-3147-4ec7-8efb-30a447fef129', + retryPolicy: null, + attempt: 1, + workflowExecutionExpirationTime: null, + cronSchedule: '', + firstWorkflowTaskBackoff: '0s', + memo: null, + searchAttributes: { + indexedFields: { + CustomBoolField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'Qm9vbA==', + }, + data: 'dHJ1ZQ==', + }, + CustomDatetimeField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'RGF0ZXRpbWU=', + }, + data: 'IjIwMjItMDQtMjhUMDU6MzA6MTkuNDI2MTkyMDg4WiI=', + }, + CustomDoubleField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'RG91Ymxl', + }, + data: 'MA==', + }, + CustomIntField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'SW50', + }, + data: 'MA==', + }, + CustomKeywordField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZA==', + }, + data: 'InJhaW5ib3ctc3RhdHVzZXMtZDQ5NGM0Ig==', + }, + CustomStringField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'VGV4dA==', + }, + data: 'InJhaW5ib3cgc3RhdHVzZXMgZDQ5NGM0IFJ1bm5pbmci', + }, + }, + }, + prevAutoResetPoints: null, + header: { + fields: {}, + }, + }, + }, + { + eventId: '2', + eventTime: '2022-04-28T05:30:19.427264889Z', + eventType: 'WorkflowTaskScheduled', + version: '0', + taskId: '1367517', + workflowTaskScheduledEventAttributes: { + taskQueue: { + name: 'rainbow-statuses', + kind: 'Normal', + }, + startToCloseTimeout: '10s', + attempt: 1, + }, + }, + { + eventId: '3', + eventTime: '2022-04-28T05:30:19.436957880Z', + eventType: 'WorkflowExecutionSignaled', + version: '0', + taskId: '1367521', + workflowExecutionSignaledEventAttributes: { + signalName: 'customSignal', + input: { + payloads: [ + { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + }, + data: 'eyJIZXkiOiJmcm9tIE1hcnMiLCJBdCI6IjIwMjItMDQtMjhUMDE6MzA6MTkuNDM0MjYyMzM1LTA0OjAwIn0=', + }, + ], + }, + identity: '168773@user0@', + header: { + fields: {}, + }, + }, + }, + { + eventId: '4', + eventTime: '2022-04-28T05:30:19.440848942Z', + eventType: 'WorkflowTaskStarted', + version: '0', + taskId: '1367523', + workflowTaskStartedEventAttributes: { + scheduledEventId: '2', + identity: '168631@user0@', + requestId: 'c2a89d60-2c1b-4a8f-b312-f11385722e7e', + }, + }, + { + eventId: '5', + eventTime: '2022-04-28T05:30:19.445592990Z', + eventType: 'WorkflowTaskCompleted', + version: '0', + taskId: '1367529', + workflowTaskCompletedEventAttributes: { + scheduledEventId: '2', + startedEventId: '4', + identity: '168631@user0@', + binaryChecksum: '04f0fb34cfd90d692fa1d506c626a598', + }, + }, + { + eventId: '6', + eventTime: '2022-04-28T05:30:19.445631589Z', + eventType: 'ActivityTaskScheduled', + version: '0', + taskId: '1367530', + activityTaskScheduledEventAttributes: { + activityId: '6', + activityType: { + name: 'LongActivity', + }, + namespace: '', + taskQueue: { + name: 'rainbow-statuses', + kind: 'Normal', + }, + header: { + fields: {}, + }, + input: null, + scheduleToCloseTimeout: '0s', + scheduleToStartTimeout: '0s', + startToCloseTimeout: '3600s', + heartbeatTimeout: '0s', + workflowTaskCompletedEventId: '5', + retryPolicy: { + initialInterval: '1s', + backoffCoefficient: 2, + maximumInterval: '100s', + maximumAttempts: 1, + nonRetryableErrorTypes: [], + }, + }, + }, + { + eventId: '7', + eventTime: '2022-04-28T05:30:19.427247101Z', + eventType: 'WorkflowExecutionCompleted', + version: '0', + taskId: '1367516', + workflowExecutionCompletedEventAttributes: { + workflowType: { + name: 'RainbowStatusesWorkflow', + }, + parentWorkflowNamespace: '', + parentWorkflowExecution: null, + parentInitiatedEventId: '0', + taskQueue: { + name: 'rainbow-statuses', + kind: 'Normal', + }, + identity: '168773@user0@', + newExecutionRunId: '4284ef9a-947f-4db2-bf15-dbc377e71fa6', + prevAutoResetPoints: null, + header: { + fields: {}, + }, + }, + }, + ], + }, + rawHistory: [], + nextPageToken: null, + archived: false, +}; + +export const EVENT_HISTORY_API = '**/api/v1/namespaces/*/workflows/*/history?*'; + +export const EVENT_HISTORY_API_REVERSE = + '**/api/v1/namespaces/*/workflows/*/history-reverse?*'; + +export const mockEventHistoryApi = ( + page: Page, + history: GetWorkflowExecutionHistoryResponse = mockEventHistory, +) => { + const ascending = page.route(EVENT_HISTORY_API, (route) => { + return route.fulfill({ + json: history, + }); + }); + + const descending = page.route(EVENT_HISTORY_API_REVERSE, (route) => { + return route.fulfill({ + json: { + ...mockEventHistory, + history: { + events: mockEventHistory.history.events.reverse(), + }, + }, + }); + }); + + return Promise.all([ascending, descending]); +}; diff --git a/tests/test-utilities/mocks/namespace.ts b/tests/test-utilities/mocks/namespace.ts new file mode 100644 index 000000000..af255c38f --- /dev/null +++ b/tests/test-utilities/mocks/namespace.ts @@ -0,0 +1,78 @@ +import type { Page } from '@playwright/test'; + +export const NAMESPACE_API = '**/api/v1/namespaces/*?'; + +const MOCK_ARCHIVED_NAMESPACE = { + namespaceInfo: { + name: 'some-archived-namespace', + state: 'Registered', + description: '', + ownerEmail: '', + data: {}, + id: '5411056f-9bd0-4b4e-90fa-e88e3031a0d0', + }, + config: { + workflowExecutionRetentionTtl: '259200s', + badBinaries: { + binaries: {}, + }, + historyArchivalState: 'Enabled', + historyArchivalUri: '', + visibilityArchivalState: 'Enabled', + visibilityArchivalUri: '', + }, + replicationConfig: { + activeClusterName: 'active', + clusters: [ + { + clusterName: 'active', + }, + ], + state: 'Unspecified', + }, + failoverVersion: '0', + isGlobalNamespace: false, +}; + +const MOCK_DEFAULT_NAMESPACE = { + namespaceInfo: { + name: 'default', + state: 'Registered', + description: '', + ownerEmail: '', + data: {}, + id: 'bbe0d4ea-c682-4c5a-bc4b-fadb8e8c8bfe', + supportsSchedules: true, + }, + config: { + workflowExecutionRetentionTtl: '86400s', + badBinaries: { + binaries: {}, + }, + historyArchivalState: 'Disabled', + historyArchivalUri: '', + visibilityArchivalState: 'Disabled', + visibilityArchivalUri: '', + customSearchAttributeAliases: {}, + }, + replicationConfig: { + activeClusterName: 'active', + clusters: [ + { + clusterName: 'active', + }, + ], + state: 'Unspecified', + }, + failoverVersion: '0', + isGlobalNamespace: false, + failoverHistory: [], +}; + +export const mockNamespaceApi = (page: Page, archived = false) => { + return page.route(NAMESPACE_API, (route) => { + route.fulfill({ + json: archived ? MOCK_ARCHIVED_NAMESPACE : MOCK_DEFAULT_NAMESPACE, + }); + }); +}; diff --git a/tests/test-utilities/mocks/namespaces.ts b/tests/test-utilities/mocks/namespaces.ts new file mode 100644 index 000000000..bca3e4c7f --- /dev/null +++ b/tests/test-utilities/mocks/namespaces.ts @@ -0,0 +1,111 @@ +import { Page } from '@playwright/test'; + +export const NAMESPACES_API = '**/api/v1/namespaces?'; + +const MOCK_NAMESPACES = { + namespaces: [ + { + namespaceInfo: { + name: 'temporal-system', + state: 'Registered', + description: 'Temporal internal system namespace', + ownerEmail: 'temporal-core@temporal.io', + data: {}, + id: '32049b68-7872-4094-8e63-d0dd59896a83', + }, + config: { + workflowExecutionRetentionTtl: '604800s', + badBinaries: { + binaries: {}, + }, + historyArchivalState: 'Disabled', + historyArchivalUri: '', + visibilityArchivalState: 'Disabled', + visibilityArchivalUri: '', + }, + replicationConfig: { + activeClusterName: 'active', + clusters: [ + { + clusterName: 'active', + }, + ], + state: 'Unspecified', + }, + failoverVersion: '0', + isGlobalNamespace: false, + }, + { + namespaceInfo: { + name: 'default', + state: 'Registered', + description: 'Default namespace for Temporal Server.', + ownerEmail: '', + data: {}, + id: '398cb3a0-112f-4a36-991c-766dd8001649', + }, + config: { + workflowExecutionRetentionTtl: '86400s', + badBinaries: { + binaries: {}, + }, + historyArchivalState: 'Disabled', + historyArchivalUri: '', + visibilityArchivalState: 'Disabled', + visibilityArchivalUri: '', + }, + replicationConfig: { + activeClusterName: 'us-east1', + clusters: [ + { + clusterName: 'us-east1', + }, + { + clusterName: 'us-east2', + }, + ], + state: 'Unspecified', + }, + failoverVersion: '0', + isGlobalNamespace: false, + }, + { + namespaceInfo: { + name: 'some-other-namespace', + state: 'Registered', + description: '', + ownerEmail: '', + data: {}, + id: '5411056f-9bd0-4b4e-90fa-e88e3031a0d0', + }, + config: { + workflowExecutionRetentionTtl: '259200s', + badBinaries: { + binaries: {}, + }, + historyArchivalState: 'Enabled', + historyArchivalUri: '', + visibilityArchivalState: 'Enabled', + visibilityArchivalUri: '', + }, + replicationConfig: { + activeClusterName: 'active', + clusters: [ + { + clusterName: 'active', + }, + ], + state: 'Unspecified', + }, + failoverVersion: '0', + isGlobalNamespace: false, + }, + ], + nextPageToken: null, +}; + +export const mockNamespacesApi = (page: Page) => { + return page.route(NAMESPACES_API, (route) => { + route.fulfill({ json: MOCK_NAMESPACES }); + }); +}; diff --git a/tests/test-utilities/mocks/query.ts b/tests/test-utilities/mocks/query.ts new file mode 100644 index 000000000..e39bfe7d8 --- /dev/null +++ b/tests/test-utilities/mocks/query.ts @@ -0,0 +1,21 @@ +import { Page } from '@playwright/test'; + +const json = { + queryResult: { + payloads: [ + { + an: 'error', + }, + ], + }, + queryRejected: null, +}; + +export default async function mockQueryApiWithStackTraceError(page: Page) { + await page.route( + '**/api/v1/namespaces/default/workflows/*/runs/*/query*', + async (route) => { + route.fulfill({ json }); + }, + ); +} diff --git a/tests/test-utilities/mocks/schedules.ts b/tests/test-utilities/mocks/schedules.ts new file mode 100644 index 000000000..34ab10905 --- /dev/null +++ b/tests/test-utilities/mocks/schedules.ts @@ -0,0 +1,142 @@ +import type { Page } from '@playwright/test'; + +export const SCHEDULES_API = '**/api/v1/namespaces/*/schedules**'; +export const SCHEDULE_API = '**/api/v1/namespaces/*/schedules/**'; + +export const mockListSchedule = { + scheduleId: 'test-schedule', + memo: null, + searchAttributes: { + indexedFields: { + TemporalNamespaceDivision: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZA==', + }, + data: 'IlRlbXBvcmFsU2NoZWR1bGVyIg==', + }, + }, + }, + info: { + spec: { + structuredCalendar: [], + cronString: [], + calendar: [], + interval: [ + { + interval: '300s', + phase: '0s', + }, + ], + excludeCalendar: [], + excludeStructuredCalendar: [], + startTime: null, + endTime: null, + jitter: null, + timezoneName: '', + timezoneData: null, + }, + workflowType: { + name: 'run-regularly', + }, + notes: '', + paused: false, + recentActions: [], + futureActionTimes: [ + '2024-01-17T16:00:00Z', + '2024-01-17T16:05:00Z', + '2024-01-17T16:10:00Z', + '2024-01-17T16:15:00Z', + '2024-01-17T16:20:00Z', + ], + }, +}; + +export const mockSchedule = { + schedule: { + spec: { + interval: [ + { + interval: '300s', + phase: '0s', + }, + ], + }, + action: { + startWorkflow: { + workflowId: 'test123', + workflowType: { + name: 'run-regularly', + }, + taskQueue: { + name: 'test', + kind: 'TASK_QUEUE_KIND_NORMAL', + }, + }, + }, + policies: { + overlapPolicy: 'SCHEDULE_OVERLAP_POLICY_SKIP', + catchupWindow: '31536000s', + }, + state: {}, + }, + info: { + actionCount: '1', + runningWorkflows: [ + { + workflowId: 'test123-2025-04-14T18:50:00Z', + runId: '019635a3-0bc8-7c15-86dd-9cdd1c8da680', + }, + ], + recentActions: [ + { + scheduleTime: '2025-04-14T18:50:00Z', + actualTime: '2025-04-14T18:50:00.009436Z', + startWorkflowResult: { + workflowId: 'test123-2025-04-14T18:50:00Z', + runId: '019635a3-0bc8-7c15-86dd-9cdd1c8da680', + }, + startWorkflowStatus: 'WORKFLOW_EXECUTION_STATUS_RUNNING', + }, + ], + futureActionTimes: [ + '2025-04-14T18:55:00Z', + '2025-04-14T19:00:00Z', + '2025-04-14T19:05:00Z', + '2025-04-14T19:10:00Z', + '2025-04-14T19:15:00Z', + '2025-04-14T19:20:00Z', + '2025-04-14T19:25:00Z', + '2025-04-14T19:30:00Z', + '2025-04-14T19:35:00Z', + '2025-04-14T19:40:00Z', + ], + createTime: '2025-04-14T18:48:29.532800Z', + }, + conflictToken: 'AAAAAAAAAAE=', +}; + +export const mockSchedulesApi = (page: Page, empty = false) => { + return page.route(SCHEDULES_API, (route) => { + const json = { + schedules: empty ? [] : [mockListSchedule], + nextPageToken: null, + }; + + console.log( + '📦 Mocking schedules API response:', + JSON.stringify(json, null, 2), + ); + console.log('🔗 Request URL:', route.request().url()); + + return route.fulfill({ json }); + }); +}; + +export const mockScheduleApi = (page: Page) => { + return page.route(SCHEDULE_API, (route) => { + return route.fulfill({ + json: mockSchedule, + }); + }); +}; diff --git a/tests/test-utilities/mocks/search-attributes.ts b/tests/test-utilities/mocks/search-attributes.ts new file mode 100644 index 000000000..1d773514c --- /dev/null +++ b/tests/test-utilities/mocks/search-attributes.ts @@ -0,0 +1,57 @@ +import type { Page } from '@playwright/test'; + +import { + SEARCH_ATTRIBUTE_TYPE, + type SearchAttributesResponse, +} from '$src/lib/types/workflows'; + +export const SEARCH_ATTRIBUTES_API = + '**/api/v1/namespaces/*/search-attributes?'; + +const MOCK_SEARCH_ATTRIBUTES: SearchAttributesResponse = { + systemAttributes: { + BatcherNamespace: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + BatcherUser: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + BinaryChecksums: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + CloseTime: SEARCH_ATTRIBUTE_TYPE.DATETIME, + CustomBoolField: SEARCH_ATTRIBUTE_TYPE.BOOL, + CustomDatetimeField: SEARCH_ATTRIBUTE_TYPE.DATETIME, + CustomDoubleField: SEARCH_ATTRIBUTE_TYPE.DOUBLE, + CustomIntField: SEARCH_ATTRIBUTE_TYPE.INT, + CustomKeywordField: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + CustomStringField: SEARCH_ATTRIBUTE_TYPE.TEXT, + CustomTextField: SEARCH_ATTRIBUTE_TYPE.TEXT, + ExecutionDuration: SEARCH_ATTRIBUTE_TYPE.INT, + ExecutionStatus: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + ExecutionTime: SEARCH_ATTRIBUTE_TYPE.DATETIME, + HistoryLength: SEARCH_ATTRIBUTE_TYPE.INT, + RunId: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + StartTime: SEARCH_ATTRIBUTE_TYPE.DATETIME, + StateTransitionCount: SEARCH_ATTRIBUTE_TYPE.INT, + TaskQueue: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + TemporalChangeVersion: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + WorkflowId: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + WorkflowType: SEARCH_ATTRIBUTE_TYPE.KEYWORD, + }, + customAttributes: {}, + storageSchema: {}, +}; + +const mergeSearchAttributes = ( + searchAttributes: Partial, +) => { + return { + ...MOCK_SEARCH_ATTRIBUTES, + ...searchAttributes, + }; +}; + +export const mockSearchAttributesApi = ( + page: Page, + searchAttributes?: Partial, +) => { + return page.route(SEARCH_ATTRIBUTES_API, (route) => { + const payload = mergeSearchAttributes(searchAttributes); + route.fulfill({ json: payload }); + }); +}; diff --git a/tests/test-utilities/mocks/settings.ts b/tests/test-utilities/mocks/settings.ts new file mode 100644 index 000000000..31e13b3fe --- /dev/null +++ b/tests/test-utilities/mocks/settings.ts @@ -0,0 +1,42 @@ +import type { Page } from '@playwright/test'; + +import { SettingsResponse } from '$src/lib/types'; + +export const SETTINGS_API = '**/api/v1/settings**'; + +const defaultSettings = { + Auth: { + Enabled: false, + Options: null, + }, + BannerText: '', + DefaultNamespace: '', + ShowTemporalSystemNamespace: false, + FeedbackURL: '', + NotifyOnNewVersion: false, + Codec: { + Endpoint: '', + PassAccessToken: false, + IncludeCredentials: false, + }, + Version: '2.22.0', + DisableWriteActions: false, + WorkflowTerminateDisabled: false, + WorkflowCancelDisabled: false, + WorkflowSignalDisabled: false, + WorkflowResetDisabled: false, + StartWorkflowDisabled: true, + BatchActionsDisabled: false, + HideWorkflowQueryErrors: false, + RefreshWorkflowCountsDisabled: false, + ActivityCommandsDisabled: false, +}; + +export const mockSettingsApi = async ( + page: Page, + customSettings: Partial = {}, +) => { + await page.route(SETTINGS_API, async (route) => { + route.fulfill({ json: { ...defaultSettings, ...customSettings } }); + }); +}; diff --git a/tests/test-utilities/mocks/system-info.ts b/tests/test-utilities/mocks/system-info.ts new file mode 100644 index 000000000..924aa6aa9 --- /dev/null +++ b/tests/test-utilities/mocks/system-info.ts @@ -0,0 +1,29 @@ +import type { Page } from '@playwright/test'; + +import { GetSystemInfoResponse } from '$src/lib/types'; + +export const SYSTEM_INFO_API = '**/api/v1/system-info**'; + +const defaultSystemInfo = { + serverVersion: '1.27.0', + capabilities: { + signalAndQueryHeader: true, + internalErrorDifferentiation: true, + activityFailureIncludeHeartbeat: true, + supportsSchedules: true, + encodedFailureAttributes: true, + buildIdBasedVersioning: true, + upsertMemo: true, + eagerWorkflowStart: true, + sdkMetadata: true, + countGroupByExecutionStatus: true, + }, +}; +export const mockSystemInfoApi = async ( + page: Page, + systemInfo: Partial = {}, +) => { + await page.route(SYSTEM_INFO_API, async (route) => { + route.fulfill({ json: { ...defaultSystemInfo, ...systemInfo } }); + }); +}; diff --git a/tests/test-utilities/mocks/task-queues.ts b/tests/test-utilities/mocks/task-queues.ts new file mode 100644 index 000000000..2057c6401 --- /dev/null +++ b/tests/test-utilities/mocks/task-queues.ts @@ -0,0 +1,20 @@ +import type { Page } from '@playwright/test'; + +export const TASK_QUEUES_API = '**/api/v1/namespaces/*/task-queues/*?*'; + +const mockTaskQueues = { + pollers: [ + { + lastAccessTime: '2022-05-05T21:42:46.576609378Z', + identity: '@poller', + ratePerSecond: 100000, + }, + ], + taskQueueStatus: null, +}; + +export const mockTaskQueuesApi = (page: Page) => { + return page.route(TASK_QUEUES_API, (route) => { + return route.fulfill({ json: mockTaskQueues }); + }); +}; diff --git a/tests/test-utilities/mocks/workflow.ts b/tests/test-utilities/mocks/workflow.ts new file mode 100644 index 000000000..27186faf1 --- /dev/null +++ b/tests/test-utilities/mocks/workflow.ts @@ -0,0 +1,261 @@ +import type { Page } from '@playwright/test'; + +import type { WorkflowExecutionAPIResponse } from '$src/lib/types/workflows'; + +export const WORKFLOW_API = '**/api/v1/namespaces/*/workflows/*?'; +export const WORKFLOW_RESET_API = '**/api/v1/namespaces/*/workflows/*/reset*'; +export const WORKFLOW_TERMINATE_API = + '**/api/v1/namespaces/*/workflows/*/terminate*'; + +export const mockWorkflow = { + executionConfig: { + taskQueue: { + name: 'rainbow-statuses', + kind: 'Normal', + }, + workflowExecutionTimeout: '0s', + workflowRunTimeout: '0s', + defaultWorkflowTaskTimeout: '10s', + }, + workflowExecutionInfo: { + execution: { + workflowId: '09db15_Running', + runId: '4284ef9a-947f-4db2-bf15-dbc377e71fa6', + }, + type: { + name: 'RainbowStatusesWorkflow', + }, + startTime: '2022-04-28T05:50:48.264756929Z', + closeTime: null, + status: 'Running', + historyLength: '6', + parentNamespaceId: '', + parentExecution: null, + historySizeBytes: '', + executionTime: '2022-04-28T05:50:48.264756929Z', + memo: { + fields: {}, + }, + searchAttributes: { + indexedFields: { + BinaryChecksums: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZA==', + }, + data: 'WyIzN2VhNzVjMzc4MDFjMTY2N2IyOThlNGYxZjk1NWE3MCJd', + }, + CustomBoolField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'Qm9vbA==', + }, + data: 'dHJ1ZQ==', + }, + CustomDatetimeField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'RGF0ZXRpbWU=', + }, + data: 'IjIwMjItMDQtMjhUMDU6NTA6NDguMjYzNTE2MDVaIg==', + }, + CustomDoubleField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'RG91Ymxl', + }, + data: 'MA==', + }, + CustomIntField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'SW50', + }, + data: 'MA==', + }, + CustomKeywordField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZA==', + }, + data: 'InJhaW5ib3ctc3RhdHVzZXMtMDlkYjE1Ig==', + }, + CustomStringField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'VGV4dA==', + }, + data: 'InJhaW5ib3cgc3RhdHVzZXMgMDlkYjE1IFJ1bm5pbmci', + }, + }, + }, + autoResetPoints: { + points: [ + { + binaryChecksum: '37ea75c37801c1667b298e4f1f955a70', + runId: '4284ef9a-947f-4db2-bf15-dbc377e71fa6', + firstWorkflowTaskCompletedId: '5', + createTime: '2022-04-28T05:50:48.291718480Z', + expireTime: null, + resettable: true, + }, + ], + }, + taskQueue: 'rainbow-statuses', + stateTransitionCount: '5', + }, + pendingActivities: [ + { + activityId: '6', + activityType: { + name: 'LongActivity', + }, + state: 'Started', + heartbeatDetails: null, + lastHeartbeatTime: '2022-04-28T05:50:48.306477858Z', + lastStartedTime: '2022-04-28T05:50:48.306477858Z', + attempt: 1, + maximumAttempts: 1, + scheduledTime: null, + expirationTime: '0001-01-01T00:00:00Z', + lastFailure: null, + lastWorkerIdentity: '173471@user0@', + }, + ], + pendingChildren: [], +} satisfies WorkflowExecutionAPIResponse; + +export const mockCompletedWorkflow = { + executionConfig: { + taskQueue: { + name: 'await_signals', + kind: 'TASK_QUEUE_KIND_NORMAL', + }, + workflowExecutionTimeout: '0s', + workflowRunTimeout: '0s', + defaultWorkflowTaskTimeout: '10s', + }, + workflowExecutionInfo: { + execution: { + workflowId: 'await_signals_f1483b17-152e-442d-a8c0-145682f76d4f', + runId: '5776fab0-f316-4880-9fa5-6ebb2293a1c5', + }, + type: { + name: 'AwaitSignalsWorkflow', + }, + startTime: '2024-05-17T15:25:26.888913Z', + closeTime: '2024-05-17T15:25:30.921605Z', + status: 'WORKFLOW_EXECUTION_STATUS_COMPLETED', + historyLength: '16', + executionTime: '2024-05-17T15:25:26.888913Z', + memo: {}, + searchAttributes: { + indexedFields: { + BuildIds: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZExpc3Q=', + }, + data: 'WyJ1bnZlcnNpb25lZCIsInVudmVyc2lvbmVkOjYzNTliM2IxYWQwYTZhYWY3N2M3NTdlODU0NzA3YTA1Il0=', + }, + }, + }, + autoResetPoints: { + points: [ + { + buildId: '6359b3b1ad0a6aaf77c757e854707a05', + runId: '5776fab0-f316-4880-9fa5-6ebb2293a1c5', + firstWorkflowTaskCompletedId: '5', + createTime: '2024-05-17T15:25:26.894727Z', + resettable: true, + }, + ], + }, + taskQueue: 'await_signals', + stateTransitionCount: '10', + historySizeBytes: '1668', + mostRecentWorkerVersionStamp: { + buildId: '6359b3b1ad0a6aaf77c757e854707a05', + }, + }, +} satisfies WorkflowExecutionAPIResponse; + +export const mockResetWorkflow: WorkflowExecutionAPIResponse = { + executionConfig: { + taskQueue: { + name: 'default', + kind: 'TASK_QUEUE_KIND_NORMAL', + }, + defaultWorkflowTaskTimeout: '10s', + }, + workflowExecutionInfo: { + execution: { + workflowId: '09db17_Running', + runId: '4284ef9a-947f-4db2-bf15-dbc377e71fa7', + }, + type: { + name: 'failing', + }, + startTime: '2025-04-28T19:36:37.170960Z', + closeTime: '2025-04-28T19:36:37.214083Z', + status: 'WORKFLOW_EXECUTION_STATUS_FAILED', + historyLength: '14', + executionTime: '2025-04-28T19:36:37.170960Z', + memo: {}, + searchAttributes: { + indexedFields: { + BuildIds: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZExpc3Q=', + }, + data: 'WyJ1bnZlcnNpb25lZCIsInVudmVyc2lvbmVkOkB0ZW1wb3JhbGlvL3dvcmtlckAxLjExLjcrMzkyYWNkMTVkNzVjYjZjZTlmNjY4YzZlNjJjOTdmMTk0MzBkZmU4ODc4YTRmZjljZGYxYWQ5MGFlMzA1ODkwMCJd', + }, + }, + }, + autoResetPoints: { + points: [ + { + buildId: + '@temporalio/worker@1.11.7+392acd15d75cb6ce9f668c6e62c97f19430dfe8878a4ff9cdf1ad90ae3058900', + runId: '874c3bed-52ef-462e-9b27-195ef359f11c', + firstWorkflowTaskCompletedId: '7', + createTime: '2025-04-28T19:36:37.195767Z', + resettable: true, + }, + ], + }, + taskQueue: 'workflow-statuses', + stateTransitionCount: '8', + historySizeBytes: '2713', + mostRecentWorkerVersionStamp: { + buildId: + '@temporalio/worker@1.11.7+392acd15d75cb6ce9f668c6e62c97f19430dfe8878a4ff9cdf1ad90ae3058900', + }, + executionDuration: '0.043123s', + }, + workflowExtendedInfo: { + originalStartTime: '2025-04-28T19:36:14.998303Z', + resetRunId: 'reset-run-id', + }, +} satisfies WorkflowExecutionAPIResponse; + +export const mockWorkflowApi = ( + page: Page, + workflow: WorkflowExecutionAPIResponse = mockWorkflow, +) => { + return page.route(WORKFLOW_API, (route) => { + return route.fulfill({ + json: workflow, + }); + }); +}; + +export const mockWorkflowResetApi = (page: Page) => { + return page.route(WORKFLOW_RESET_API, (route) => { + return route.fulfill({ + json: {}, + }); + }); + return; +}; diff --git a/tests/test-utilities/mocks/workflows-count.ts b/tests/test-utilities/mocks/workflows-count.ts new file mode 100644 index 000000000..a67dc1d74 --- /dev/null +++ b/tests/test-utilities/mocks/workflows-count.ts @@ -0,0 +1,93 @@ +import type { Page } from '@playwright/test'; + +export const WORKFLOWS_COUNT_API = '**/api/v1/namespaces/*/workflow-count?*'; + +export const mockWorkflowsCountApi = (page: Page, empty = false) => { + return page.route(WORKFLOWS_COUNT_API, (route) => { + return route.fulfill({ json: { count: empty ? '0' : '15' } }); + }); +}; + +const groupByCountResponse = { + count: '31230', + groups: [ + { + groupValues: [ + { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZA==', + }, + data: 'IlJ1bm5pbmci', + }, + ], + count: '6', + }, + { + groupValues: [ + { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZA==', + }, + data: 'IkNvbXBsZXRlZCI=', + }, + ], + count: '21652', + }, + { + groupValues: [ + { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZA==', + }, + data: 'IkZhaWxlZCI=', + }, + ], + count: '1932', + }, + { + groupValues: [ + { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZA==', + }, + data: 'IkNhbmNlbGVkIg==', + }, + ], + count: '6215', + }, + { + groupValues: [ + { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZA==', + }, + data: 'IlRlcm1pbmF0ZWQi', + }, + ], + count: '917', + }, + { + groupValues: [ + { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZA==', + }, + data: 'IlRpbWVkT3V0Ig==', + }, + ], + count: '508', + }, + ], +}; + +export const mockWorkflowsGroupByCountApi = (page: Page) => { + return page.route(WORKFLOWS_COUNT_API, (route) => { + return route.fulfill({ json: groupByCountResponse }); + }); +}; diff --git a/tests/test-utilities/mocks/workflows.ts b/tests/test-utilities/mocks/workflows.ts new file mode 100644 index 000000000..96fc96dcd --- /dev/null +++ b/tests/test-utilities/mocks/workflows.ts @@ -0,0 +1,90 @@ +import type { Page } from '@playwright/test'; + +export const WORKFLOWS_API = '**/api/v1/namespaces/*/workflows**'; + +const mockWorkflow = { + execution: { + workflowId: '002c98_Running', + runId: '54a1a2c3-9322-4f75-88ea-bf5660c251d0', + }, + type: { + name: 'ImportantWorkflowType', + }, + startTime: '2022-03-23T18:06:01.726484047Z', + closeTime: null, + status: 'Running', + historyLength: '0', + parentNamespaceId: '', + parentExecution: null, + executionTime: '2022-03-23T18:06:01.726484047Z', + memo: { + fields: {}, + }, + searchAttributes: { + indexedFields: { + BinaryChecksums: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZA==', + }, + data: 'WyIzMmUwNDI1OWUzYTFlMTM3ZmE2Njg5M2JiNjE3OTc5YSJd', + }, + CustomBoolField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'Qm9vbA==', + }, + data: 'dHJ1ZQ==', + }, + CustomDatetimeField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'RGF0ZXRpbWU=', + }, + data: 'IjIwMjItMDMtMjNUMTg6MDY6MDEuNzIwMTM3WiI=', + }, + CustomDoubleField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'RG91Ymxl', + }, + data: 'MA==', + }, + CustomIntField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'SW50', + }, + data: 'MA==', + }, + CustomKeywordField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'S2V5d29yZA==', + }, + data: 'InJhaW5ib3ctc3RhdHVzZXMtMDAyYzk4Ig==', + }, + CustomStringField: { + metadata: { + encoding: 'anNvbi9wbGFpbg==', + type: 'VGV4dA==', + }, + data: 'InJhaW5ib3cgc3RhdHVzZXMgMDAyYzk4IFJ1bm5pbmci', + }, + }, + }, + autoResetPoints: null, + taskQueue: 'rainbow-statuses', + stateTransitionCount: '0', +}; + +export const mockWorkflowsApi = (page: Page) => { + return page.route(WORKFLOWS_API, (route) => { + return route.fulfill({ + json: { + executions: [mockWorkflow, mockWorkflow, mockWorkflow], + nextPageToken: null, + }, + }); + }); +}; diff --git a/tests/tsconfig.json b/tests/tsconfig.json new file mode 100644 index 000000000..2cc937ba8 --- /dev/null +++ b/tests/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "$src/*": ["../src/*"], + "~/*": ["./*"] + } + } +} diff --git a/tsconfig.json b/tsconfig.json index a21b682ba..7d4bf40c0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,15 @@ { + "extends": "./.svelte-kit/tsconfig.json", "compilerOptions": { - "moduleResolution": "node", + "moduleResolution": "bundler", // "strict": true, "module": "es2020", - "lib": ["es2020", "dom", "DOM.Iterable"], + "lib": ["es2020", "es2023", "dom", "DOM.Iterable"], "target": "es2019", /** svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript to enforce using \`import type\` instead of \`import\` for Types. */ - "importsNotUsedAsValues": "error", "isolatedModules": false, "resolveJsonModule": true, /** @@ -20,16 +20,8 @@ "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, - "baseUrl": ".", "allowJs": true, - "checkJs": true, - "paths": { - "$lib/*": ["src/lib/*"], - "$types": ["src/types"], - "$components/*": ["src/components/*"], - "$holocene/*": ["src/lib/holocene/*"], - "$fixtures/*": ["src/fixtures/*"] - } + "checkJs": true }, "include": [ "src/**/*.d.ts", @@ -37,14 +29,14 @@ "src/**/*.ts", "src/**/*.svelte", "src/lib/models/**/*.test.ts", - "cypress/**/*.ts" + "utilities/**/*.ts", + "temporal/**/*.ts", + "plugins/**/*.ts" ], "exclude": [ "src/lib/services/**/*.test.ts", "src/lib/utilities/**/*.test.ts", "src/lib/stores/**/*.test.ts", - // Until there is better TS support for Histoire - "src/histoire.setup.ts", - "src/lib/holocene/**/*.story.svelte" + "tests/**/*.ts" ] } diff --git a/utilities/temporal-server.ts b/utilities/temporal-server.ts new file mode 100644 index 000000000..cf78c77d4 --- /dev/null +++ b/utilities/temporal-server.ts @@ -0,0 +1,137 @@ +import { join } from 'path'; + +import waitForPort from 'wait-port'; +import { $, chalk } from 'zx'; + +export type TemporalServer = { + shutdown: () => Promise; + ready: () => Promise; +}; + +const localCLIPath = join(process.cwd(), 'bin', 'cli', 'temporal'); + +export type TemporalServerOptions = { + port?: number; + uiPort?: number; + path?: string; + logLevel?: string; + codecEndpoint?: string; + headless?: boolean; + dbFilename?: string; +}; + +const warn = (message: Parameters[0]) => { + console.warn(`${chalk.bgYellow.black('WARN')}: ${message}`); +}; + +const getCLIPath = async (cliPath = localCLIPath): Promise => { + const stylizedPath = chalk.yellowBright(cliPath); + + console.log(chalk.yellow(`Checking Temporal CLI at ${stylizedPath}…`)); + + const { stdout, exitCode } = await $`${cliPath} -v`.quiet().nothrow(); + + if (exitCode === 0) { + console.log( + chalk.greenBright( + `Temporal CLI found at ${stylizedPath}:\n\t`, + '→', + chalk.green(stdout.trim()), + ), + ); + + return cliPath; + } + + const { stdout: globalPath } = await $`which temporal`.nothrow(); + + if (globalPath && cliPath !== globalPath.trim()) + return getCLIPath(globalPath.trim()); + + warn("Couldn't find Temporal CLI. Skipping…"); +}; + +let temporalServer: TemporalServer; + +export const getTemporalServer = (): TemporalServer => temporalServer; + +export const createTemporalServer = async ({ + port = 7233, + uiPort = port + 1000, + path = localCLIPath, + logLevel = 'error', + codecEndpoint, + headless = false, + dbFilename, +}: TemporalServerOptions = {}) => { + const cliPath = await getCLIPath(path); + + const flags = [ + `--port=${port}`, + `--ui-port=${uiPort}`, + `--log-level=${logLevel}`, + `--http-port=${port + 1}`, + ]; + + if (codecEndpoint) { + flags.push(`--ui-codec-endpoint=${codecEndpoint}`); + } + + if (headless) { + flags.push('--headless'); + } + + if (dbFilename) { + flags.push(`--db-filename=${dbFilename}`); + } + + const temporal = + $`${cliPath} server start-dev --dynamic-config-value frontend.enableUpdateWorkflowExecution=true --dynamic-config-value frontend.enableUpdateWorkflowExecutionAsyncAccepted=true --dynamic-config-value frontend.workerVersioningDataAPIs=true --dynamic-config-value frontend.workerVersioningWorkflowAPIs=true --dynamic-config-value worker.buildIdScavengerEnabled=true --dynamic-config-value system.enableNexus=true ${flags}`.quiet(); + + temporal.catch(async ({ stdout, stderr, exitCode }) => { + console.log('EXIT CODE', exitCode); + if (exitCode) { + try { + const { error }: { error: string } = JSON.parse(stdout); + + if (error.includes('address already in use')) { + return warn( + `Port ${port} is already in use. Falling back to whatever is running on that port.`, + ); + } + } catch { + console.error(stderr); + } + } + }); + + const shutdown = async () => { + await temporal.kill(); + console.log( + `🔪 killed temporal server, exited with code: ${await temporal.exitCode}`, + ); + return await temporal.exitCode; + }; + + const ready = async () => { + const ports = [ + waitForPort({ port, output: 'silent' }), + !headless && waitForPort({ port: uiPort, output: 'silent' }), + ]; + + const portsPromise = await Promise.all(ports).then((ports) => { + console.log(`✨ temporal dev server running on port: ${port}`); + + return ports; + }); + + return portsPromise.every(({ open }) => open); + }; + + temporalServer = { + ready, + shutdown, + }; + + return temporalServer; +}; diff --git a/utilities/ui-server.ts b/utilities/ui-server.ts new file mode 100644 index 000000000..57dc7cef7 --- /dev/null +++ b/utilities/ui-server.ts @@ -0,0 +1,77 @@ +import { join } from 'path'; + +import waitForPort from 'wait-port'; +import { $ } from 'zx'; + +export type UIServer = { + shutdown: () => Promise; + ready: () => ReturnType; +}; + +let uiServer: UIServer; + +export const getUIServer = (): UIServer => { + return uiServer; +}; + +type Environemt = 'development' | 'e2e'; + +const portForEnv = (env: Environemt) => { + if (env === 'development') return 8081; + if (env === 'e2e') return 8080; +}; + +export const createUIServer = async ( + env: 'development' | 'e2e' = 'development', + options?: { verbose?: boolean }, +) => { + $.cwd = join(process.cwd(), 'server'); + + // Check for verbose mode via env var or options + const verbose = options?.verbose ?? process.env.UI_SERVER_VERBOSE === 'true'; + const hotReload = process.env.UI_SERVER_HOT_RELOAD === 'true'; + + let uiServerProcess: ReturnType; + + if (hotReload) { + // Install Air if not already available + try { + await $`which air`.quiet(); + } catch { + console.log('📦 Installing Air for hot reloading...'); + await $`go install github.com/air-verse/air@latest`; + } + + // Use Air for hot reloading in development + uiServerProcess = verbose ? $`air` : $`air`.quiet(); + console.log( + `✨ ui-server running in ${env} mode with hot reload on port ${portForEnv(env)}`, + ); + } else { + // Use traditional build for e2e + await $`make build`; + uiServerProcess = verbose + ? $`./ui-server --env ${env} start` + : $`./ui-server --env ${env} start`.quiet(); + console.log( + `✨ ui-server running in ${env} mode on port ${portForEnv(env)}`, + ); + } + + const shutdown = async () => { + await uiServerProcess.kill(); + console.log('🔪 killed ui-server'); + return await uiServerProcess.exitCode; + }; + + const ready = async () => { + return waitForPort({ port: portForEnv(env), output: 'silent' }); + }; + + uiServer = { + shutdown, + ready, + }; + + return uiServer; +}; diff --git a/vite.config.js b/vite.config.js deleted file mode 100644 index 8dc2a7e77..000000000 --- a/vite.config.js +++ /dev/null @@ -1,48 +0,0 @@ -import { sveltekit } from '@sveltejs/kit/vite'; -import { HstSvelte } from '@histoire/plugin-svelte'; -import { defaultColors } from 'histoire'; -import path from 'path'; - -/** @type {import('vite').UserConfig} */ -const config = { - plugins: [sveltekit()], - histoire: { - plugins: [HstSvelte()], - setupFile: './src/histoire.setup.ts', - storyIgnored: ['node_modules/**', 'dist/**', 'package/**'], - theme: { - title: 'Holocene', - favicon: './src/lib/vendor/favicon.ico', - logo: { - square: '/src/lib/vendor/logo-dark.svg', - dark: '/src/lib/vendor/logo.svg', - light: '/src/lib/vendor/logo-dark.svg', - }, - logoHref: 'https://temporal.io', - colors: { - gray: defaultColors.gray, - primary: defaultColors.blue, - }, - }, - vite: { - resolve: { - alias: { - $lib: path.resolve('./src/lib'), - $app: path.resolve('./src/lib/svelte-mocks/app'), - }, - }, - }, - }, - optimizeDeps: { - include: ['date-fns', 'date-fns-tz', 'websocket-as-promised'], - }, - resolve: { - alias: { - $types: path.resolve('./src/types'), - $holocene: path.resolve('./src/lib/holocene'), - $fixtures: path.resolve('./src/fixtures'), - }, - }, -}; - -export default config; diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 000000000..794ad44a9 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,28 @@ +import path from 'path'; + +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +import { temporalServer } from './plugins/vite-plugin-temporal-server'; +import { uiServerPlugin } from './plugins/vite-plugin-ui-server'; + +export default defineConfig({ + plugins: [sveltekit(), temporalServer(), uiServerPlugin()], + optimizeDeps: { + include: ['date-fns', 'date-fns-tz'], + }, + resolve: { + alias: { + $types: path.resolve('./src/types'), + $fixtures: path.resolve('./src/fixtures'), + $components: path.resolve('./src/lib/components/'), + $holocene: path.resolve('./src/lib/holocene'), + }, + }, + server: { + port: 3000, + }, + preview: { + port: 3000, + }, +}); diff --git a/vitest-setup.ts b/vitest-setup.ts new file mode 100644 index 000000000..7896b77f3 --- /dev/null +++ b/vitest-setup.ts @@ -0,0 +1,32 @@ +import i18next from 'i18next'; +import { vi } from 'vitest'; + +import { i18nNamespaces } from './src/lib/i18n'; +import resources from './src/lib/i18n/locales'; + +i18next.init({ + fallbackLng: 'en', + load: 'languageOnly', + ns: i18nNamespaces, + defaultNS: 'common', + detection: { + order: ['querystring', 'localStorage', 'navigator'], + caches: ['localStorage'], + lookupQuerystring: 'lng', + lookupLocalStorage: 'locale', + }, + resources, +}); + +const BroadcastChannelMock = vi.fn(() => ({ + addEventListener: () => {}, + postMessage: () => {}, +})); + +vi.stubGlobal('BroadcastChannel', BroadcastChannelMock); + +vi.mock('esm-env', () => { + const BROWSER = true; + const DEV = false; + return { BROWSER, DEV }; +}); diff --git a/vitest.config.ts b/vitest.config.ts index 9347a80aa..6924eeeda 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,15 +1,16 @@ -import { configDefaults, defineConfig } from 'vitest/config'; -import { svelte } from '@sveltejs/vite-plugin-svelte'; import path from 'path'; +import { svelte } from '@sveltejs/vite-plugin-svelte'; +import { defineConfig } from 'vite'; +import { configDefaults } from 'vitest/config'; + export default defineConfig({ - plugins: [svelte({ hot: !process.env.VITEST })], + plugins: [svelte({ hot: false })], resolve: { alias: { $lib: path.resolve(__dirname, './src/lib'), $types: path.resolve(__dirname, './src/types'), $components: path.resolve(__dirname, './src/lib/components/'), - $holocene: path.resolve(__dirname, './src/lib/holocene/'), $app: path.resolve(__dirname, './src/lib/svelte-mocks/app/'), $fixtures: path.resolve(__dirname, './src/fixtures/'), }, @@ -25,8 +26,16 @@ export default defineConfig({ '**/*.test.ts', ], }, + exclude: [ + ...configDefaults.exclude, + 'package', + 'build', + 'e2e', + 'tests', + '.svelte-kit', + ], environment: 'jsdom', - setupFiles: ['./vitest_setup.ts'], + setupFiles: ['./vitest-setup.ts', 'vitest-localstorage-mock'], deps: { inline: ['date-fns'], }, diff --git a/vitest_setup.ts b/vitest_setup.ts deleted file mode 100644 index a663cad3c..000000000 --- a/vitest_setup.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { vi } from 'vitest'; - -const BroadcastChannelMock = vi.fn(() => ({ - // eslint-disable-next-line @typescript-eslint/no-empty-function - addEventListener: () => {}, - // eslint-disable-next-line @typescript-eslint/no-empty-function - postMessage: () => {}, -})); - -vi.stubGlobal('BroadcastChannel', BroadcastChannelMock);