diff --git a/.github/workflows/pr_verify_linked_issue.yml b/.github/workflows/pr_verify_linked_issue.yml new file mode 100644 index 0000000000..adf2718a46 --- /dev/null +++ b/.github/workflows/pr_verify_linked_issue.yml @@ -0,0 +1,20 @@ +# This workflow will inspect a pull request to ensure there is a linked issue or a +# valid issue is mentioned in the body. If neither is present it fails the check and adds +# a comment alerting users of this missing requirement. +name: VerifyIssue + +on: + pull_request: + types: [edited, synchronize, opened, reopened] + check_run: + +jobs: + verify_linked_issue: + runs-on: ubuntu-latest + name: Ensure Pull Request has a linked issue. + steps: + - name: Verify Linked Issue + uses: hattan/verify-linked-issue-action@v1.1.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + diff --git a/.gitignore b/.gitignore index 01a9526908..e876823dcd 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,7 @@ frontend/cypress.env.json frontend/*.env pkg/query-service/signoz.db -pkg/query-service/tframe/test-deploy/data/ +pkg/query-service/tests/test-deploy/data/ # local data diff --git a/.scripts/commentLinesForSetup.sh b/.scripts/commentLinesForSetup.sh index 7ea6b468ad..c0dfd40e9f 100644 --- a/.scripts/commentLinesForSetup.sh +++ b/.scripts/commentLinesForSetup.sh @@ -4,4 +4,4 @@ # Update the Line Numbers when deploy/docker/clickhouse-setup/docker-compose.yaml chnages. # Docs Ref.: https://github.com/SigNoz/signoz/blob/main/CONTRIBUTING.md#contribute-to-frontend-with-docker-installation-of-signoz -sed -i 38,70's/.*/# &/' .././deploy/docker/clickhouse-setup/docker-compose.yaml +sed -i 38,62's/.*/# &/' .././deploy/docker/clickhouse-setup/docker-compose.yaml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d0e7a7169b..6205d85884 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,10 +18,10 @@ Need to update [https://github.com/SigNoz/signoz/tree/main/frontend](https://git ### Contribute to Frontend with Docker installation of SigNoz - `git clone https://github.com/SigNoz/signoz.git && cd signoz` -- comment out frontend service section at `deploy/docker/clickhouse-setup/docker-compose.yaml#L59` +- comment out frontend service section at `deploy/docker/clickhouse-setup/docker-compose.yaml#L62` - run `cd deploy` to move to deploy directory - Install signoz locally without the frontend - - Add below configuration to query-service section at `docker/clickhouse-setup/docker-compose.yaml#L36` + - Add below configuration to query-service section at `docker/clickhouse-setup/docker-compose.yaml#L38` ```docker ports: @@ -55,9 +55,9 @@ Need to update [https://github.com/SigNoz/signoz/tree/main/pkg/query-service](ht - git clone https://github.com/SigNoz/signoz.git - run `cd signoz` to move to signoz directory - run `sudo make dev-setup` to configure local setup to run query-service -- comment out frontend service section at `docker/clickhouse-setup/docker-compose.yaml#L45` -- comment out query-service section at `docker/clickhouse-setup/docker-compose.yaml#L28` -- add below configuration to clickhouse section at `docker/clickhouse-setup/docker-compose.yaml#L6` +- comment out frontend service section at `docker/clickhouse-setup/docker-compose.yaml` +- comment out query-service section at `docker/clickhouse-setup/docker-compose.yaml` +- add below configuration to clickhouse section at `docker/clickhouse-setup/docker-compose.yaml` ```docker expose: - 9000 diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml index 6c7010963b..d0a22e95e6 100644 --- a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml @@ -24,7 +24,7 @@ services: retries: 3 alertmanager: - image: signoz/alertmanager:0.6.1 + image: signoz/alertmanager:0.23.0-0.1 volumes: - ./data/alertmanager:/data command: @@ -37,7 +37,7 @@ services: condition: on-failure query-service: - image: signoz/query-service:0.7.5 + image: signoz/query-service:0.8.0 command: ["-config=/root/config/prometheus.yml"] ports: - "8080:8080" @@ -46,7 +46,7 @@ services: - ../dashboards:/root/config/dashboards - ./data/signoz/:/var/lib/signoz/ environment: - - ClickHouseUrl=tcp://clickhouse:9000 + - ClickHouseUrl=tcp://clickhouse:9000/?database=signoz_traces - STORAGE=clickhouse - GODEBUG=netdns=go - TELEMETRY_ENABLED=true @@ -64,7 +64,7 @@ services: - clickhouse frontend: - image: signoz/frontend:0.7.5 + image: signoz/frontend:0.8.0 deploy: restart_policy: condition: on-failure @@ -77,7 +77,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector: - image: signoz/otelcontribcol:0.43.0 + image: signoz/otelcontribcol:0.43.0-0.1 command: ["--config=/etc/otel-collector-config.yaml"] volumes: - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml @@ -103,7 +103,7 @@ services: - clickhouse otel-collector-metrics: - image: signoz/otelcontribcol:0.43.0 + image: signoz/otelcontribcol:0.43.0-0.1 command: ["--config=/etc/otel-collector-metrics-config.yaml"] volumes: - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml diff --git a/deploy/docker-swarm/clickhouse-setup/otel-collector-config.yaml b/deploy/docker-swarm/clickhouse-setup/otel-collector-config.yaml index 98a336988c..9d34907164 100644 --- a/deploy/docker-swarm/clickhouse-setup/otel-collector-config.yaml +++ b/deploy/docker-swarm/clickhouse-setup/otel-collector-config.yaml @@ -28,6 +28,11 @@ processors: metrics_exporter: prometheus latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ] dimensions_cache_size: 10000 + dimensions: + - name: service.namespace + default: default + - name: deployment.environment + default: default # memory_limiter: # # 80% of maximum memory up to 2G # limit_mib: 1500 @@ -48,7 +53,7 @@ extensions: zpages: {} exporters: clickhouse: - datasource: tcp://clickhouse:9000 + datasource: tcp://clickhouse:9000/?database=signoz_traces clickhousemetricswrite: endpoint: tcp://clickhouse:9000/?database=signoz_metrics resource_to_telemetry_conversion: diff --git a/deploy/docker-swarm/common/nginx-config.conf b/deploy/docker-swarm/common/nginx-config.conf index f059d3972e..55a780e0f3 100644 --- a/deploy/docker-swarm/common/nginx-config.conf +++ b/deploy/docker-swarm/common/nginx-config.conf @@ -1,7 +1,7 @@ server { listen 3301; server_name _; - + gzip on; gzip_static on; gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; @@ -12,19 +12,21 @@ server { gzip_http_version 1.1; location / { - root /usr/share/nginx/html; - index index.html index.htm; - try_files $uri $uri/ /index.html; + add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0"; + add_header Last-Modified $date_gmt; + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; } + location /api { - proxy_pass http://query-service:8080/api; - + proxy_pass http://query-service:8080/api; } # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { - root /usr/share/nginx/html; + root /usr/share/nginx/html; } } \ No newline at end of file diff --git a/deploy/docker/clickhouse-setup/docker-compose.arm.yaml b/deploy/docker/clickhouse-setup/docker-compose.arm.yaml index 9b4f86bc05..bb5dbb5207 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.arm.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.arm.yaml @@ -22,7 +22,7 @@ services: retries: 3 alertmanager: - image: signoz/alertmanager:0.6.1 + image: signoz/alertmanager:0.23.0-0.1 volumes: - ./data/alertmanager:/data depends_on: @@ -33,8 +33,11 @@ services: - --queryService.url=http://query-service:8080 - --storage.path=/data +# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` + + query-service: - image: signoz/query-service:0.7.5 + image: signoz/query-service:0.8.0 container_name: query-service command: ["-config=/root/config/prometheus.yml"] volumes: @@ -42,7 +45,7 @@ services: - ../dashboards:/root/config/dashboards - ./data/signoz/:/var/lib/signoz/ environment: - - ClickHouseUrl=tcp://clickhouse:9000 + - ClickHouseUrl=tcp://clickhouse:9000/?database=signoz_traces - STORAGE=clickhouse - GODEBUG=netdns=go - TELEMETRY_ENABLED=true @@ -59,7 +62,7 @@ services: condition: service_healthy frontend: - image: signoz/frontend:0.7.5 + image: signoz/frontend:0.8.0 container_name: frontend restart: on-failure depends_on: @@ -71,7 +74,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector: - image: signoz/otelcontribcol:0.43.0 + image: signoz/otelcontribcol:0.43.0-0.1 command: ["--config=/etc/otel-collector-config.yaml"] volumes: - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml @@ -92,7 +95,7 @@ services: condition: service_healthy otel-collector-metrics: - image: signoz/otelcontribcol:0.43.0 + image: signoz/otelcontribcol:0.43.0-0.1 command: ["--config=/etc/otel-collector-metrics-config.yaml"] volumes: - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml index 1d7fa921e7..7f484c913d 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.yaml @@ -4,8 +4,8 @@ services: clickhouse: image: yandex/clickhouse-server:21.12.3.32 # ports: - # - "9000:9000" - # - "8123:8123" + # - "9000:9000" + # - "8123:8123" volumes: - ./clickhouse-config.xml:/etc/clickhouse-server/config.xml - ./data/clickhouse/:/var/lib/clickhouse/ @@ -22,7 +22,7 @@ services: retries: 3 alertmanager: - image: signoz/alertmanager:0.6.1 + image: signoz/alertmanager:0.23.0-0.1 volumes: - ./data/alertmanager:/data depends_on: @@ -36,7 +36,7 @@ services: # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` query-service: - image: signoz/query-service:0.7.5 + image: signoz/query-service:0.8.0 container_name: query-service command: ["-config=/root/config/prometheus.yml"] volumes: @@ -44,7 +44,7 @@ services: - ../dashboards:/root/config/dashboards - ./data/signoz/:/var/lib/signoz/ environment: - - ClickHouseUrl=tcp://clickhouse:9000 + - ClickHouseUrl=tcp://clickhouse:9000/?database=signoz_traces - STORAGE=clickhouse - GODEBUG=netdns=go - TELEMETRY_ENABLED=true @@ -60,7 +60,7 @@ services: condition: service_healthy frontend: - image: signoz/frontend:0.7.5 + image: signoz/frontend:0.8.0 container_name: frontend restart: on-failure depends_on: @@ -72,7 +72,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector: - image: signoz/otelcontribcol:0.43.0 + image: signoz/otelcontribcol:0.43.0-0.1 command: ["--config=/etc/otel-collector-config.yaml"] volumes: - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml @@ -93,7 +93,7 @@ services: condition: service_healthy otel-collector-metrics: - image: signoz/otelcontribcol:0.43.0 + image: signoz/otelcontribcol:0.43.0-0.1 command: ["--config=/etc/otel-collector-metrics-config.yaml"] volumes: - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml @@ -103,15 +103,15 @@ services: condition: service_healthy hotrod: - image: jaegertracing/example-hotrod:1.30 - container_name: hotrod - logging: - options: - max-size: 50m - max-file: "3" - command: ["all"] - environment: - - JAEGER_ENDPOINT=http://otel-collector:14268/api/traces + image: jaegertracing/example-hotrod:1.30 + container_name: hotrod + logging: + options: + max-size: 50m + max-file: "3" + command: ["all"] + environment: + - JAEGER_ENDPOINT=http://otel-collector:14268/api/traces load-hotrod: image: "grubykarol/locust:1.2.3-python3.9-alpine3.12" diff --git a/deploy/docker/clickhouse-setup/otel-collector-config.yaml b/deploy/docker/clickhouse-setup/otel-collector-config.yaml index 98a336988c..c59a8f0e87 100644 --- a/deploy/docker/clickhouse-setup/otel-collector-config.yaml +++ b/deploy/docker/clickhouse-setup/otel-collector-config.yaml @@ -28,6 +28,11 @@ processors: metrics_exporter: prometheus latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ] dimensions_cache_size: 10000 + dimensions: + - name: service.namespace + default: default + - name: deployment.environment + default: default # memory_limiter: # # 80% of maximum memory up to 2G # limit_mib: 1500 @@ -48,7 +53,7 @@ extensions: zpages: {} exporters: clickhouse: - datasource: tcp://clickhouse:9000 + datasource: tcp://clickhouse:9000/?database=signoz_traces clickhousemetricswrite: endpoint: tcp://clickhouse:9000/?database=signoz_metrics resource_to_telemetry_conversion: @@ -68,4 +73,4 @@ service: exporters: [clickhousemetricswrite] metrics/spanmetrics: receivers: [otlp/spanmetrics] - exporters: [prometheus] \ No newline at end of file + exporters: [prometheus] diff --git a/deploy/docker/clickhouse-setup/otel-collector-metrics-config.yaml b/deploy/docker/clickhouse-setup/otel-collector-metrics-config.yaml index 0563a397da..cd5ede2358 100644 --- a/deploy/docker/clickhouse-setup/otel-collector-metrics-config.yaml +++ b/deploy/docker/clickhouse-setup/otel-collector-metrics-config.yaml @@ -44,4 +44,4 @@ service: metrics: receivers: [otlp, prometheus] processors: [batch] - exporters: [clickhousemetricswrite] \ No newline at end of file + exporters: [clickhousemetricswrite] diff --git a/deploy/docker/common/nginx-config.conf b/deploy/docker/common/nginx-config.conf index 3c7a9db8f0..3444de7808 100644 --- a/deploy/docker/common/nginx-config.conf +++ b/deploy/docker/common/nginx-config.conf @@ -1,7 +1,7 @@ server { listen 3301; server_name _; - + gzip on; gzip_static on; gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; @@ -12,22 +12,25 @@ server { gzip_http_version 1.1; location / { - root /usr/share/nginx/html; - index index.html index.htm; - try_files $uri $uri/ /index.html; + add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0"; + add_header Last-Modified $date_gmt; + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri/ /index.html; } + location /api/alertmanager{ - proxy_pass http://alertmanager:9093/api/v2; + proxy_pass http://alertmanager:9093/api/v2; } + location /api { - proxy_pass http://query-service:8080/api; - + proxy_pass http://query-service:8080/api; } # redirect server error pages to the static page /50x.html # error_page 500 502 503 504 /50x.html; location = /50x.html { - root /usr/share/nginx/html; + root /usr/share/nginx/html; } } \ No newline at end of file diff --git a/frontend/.eslintignore b/frontend/.eslintignore index dd87e2d73f..545037e39a 100644 --- a/frontend/.eslintignore +++ b/frontend/.eslintignore @@ -1,2 +1,3 @@ node_modules build +*.typegen.ts diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index fb9d999579..1c1aef5939 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -101,12 +101,11 @@ module.exports = { }, }, ], + '@typescript-eslint/no-unused-vars': 'error', // eslint rules need to remove 'no-shadow': 'off', '@typescript-eslint/no-shadow': 'off', - 'global-require': 'off', - '@typescript-eslint/no-var-requires': 'off', 'import/no-cycle': 'off', 'prettier/prettier': [ diff --git a/frontend/cypress/CustomFunctions/Login.ts b/frontend/cypress/CustomFunctions/Login.ts index dba33d90aa..3d3c8f791a 100644 --- a/frontend/cypress/CustomFunctions/Login.ts +++ b/frontend/cypress/CustomFunctions/Login.ts @@ -1,5 +1,6 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions */ const Login = ({ email, name }: LoginProps): void => { - const emailInput = cy.findByPlaceholderText('mike@netflix.com'); + const emailInput = cy.findByPlaceholderText('name@yourcompany.com'); emailInput.then((emailInput) => { const element = emailInput[0]; @@ -13,7 +14,7 @@ const Login = ({ email, name }: LoginProps): void => { expect(inputValue).to.be.equals(email); }); - const firstNameInput = cy.findByPlaceholderText('Mike'); + const firstNameInput = cy.findByPlaceholderText('Your Name'); firstNameInput.then((firstNameInput) => { const element = firstNameInput[0]; // element is present diff --git a/frontend/package.json b/frontend/package.json index ed63737caf..dad3b0589e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,6 +31,7 @@ "@testing-library/react": "^11.1.0", "@testing-library/user-event": "^12.1.10", "@welldone-software/why-did-you-render": "^6.2.1", + "@xstate/react": "^3.0.0", "antd": "4.19.2", "axios": "^0.21.0", "babel-eslint": "^10.1.0", @@ -58,6 +59,7 @@ "i18next-browser-languagedetector": "^6.1.3", "i18next-http-backend": "^1.3.2", "jest": "^27.5.1", + "js-base64": "^3.7.2", "less": "^4.1.2", "less-loader": "^10.2.0", "lodash-es": "^4.17.21", @@ -85,7 +87,8 @@ "uuid": "^8.3.2", "web-vitals": "^0.2.4", "webpack": "^5.23.0", - "webpack-dev-server": "^4.3.1" + "webpack-dev-server": "^4.3.1", + "xstate": "^4.31.0" }, "browserslist": { "production": [ diff --git a/frontend/public/locales/en-GB/channels.json b/frontend/public/locales/en-GB/channels.json new file mode 100644 index 0000000000..5e670cc536 --- /dev/null +++ b/frontend/public/locales/en-GB/channels.json @@ -0,0 +1,48 @@ +{ + "page_title_create": "New Notification Channels", + "page_title_edit": "Edit Notification Channels", + "button_save_channel": "Save", + "button_test_channel": "Test", + "button_return": "Back", + "field_channel_name": "Name", + "field_channel_type": "Type", + "field_webhook_url": "Webhook URL", + "field_slack_recipient": "Recipient", + "field_slack_title": "Title", + "field_slack_description": "Description", + "field_webhook_username": "User Name (optional)", + "field_webhook_password": "Password (optional)", + "field_pager_routing_key": "Routing Key", + "field_pager_description": "Description", + "field_pager_severity": "Severity", + "field_pager_details": "Additional Information", + "field_pager_component": "Component", + "field_pager_group": "Group", + "field_pager_class": "Class", + "field_pager_client": "Client", + "field_pager_client_url": "Client URL", + "placeholder_slack_description": "Description", + "placeholder_pager_description": "Description", + "help_pager_client": "Shows up as event source in Pagerduty", + "help_pager_client_url": "Shows up as event source link in Pagerduty", + "help_pager_class": "The class/type of the event", + "help_pager_details": "Specify a key-value format (must be a valid json)", + "help_pager_group": "A cluster or grouping of sources", + "help_pager_component": "The part or component of the affected system that is broke", + "help_pager_severity": "Severity of the incident, must be one of: must be one of the following: 'critical', 'warning', 'error' or 'info'", + "help_webhook_username": "Leave empty for bearer auth or when authentication is not necessary.", + "help_webhook_password": "Specify a password or bearer token", + "help_pager_description": "Shows up as description in pagerduty", + "channel_creation_done": "Successfully created the channel", + "channel_creation_failed": "An unexpected error occurred while creating this channel", + "channel_edit_done": "Channels Edited Successfully", + "channel_edit_failed": "An unexpected error occurred while updating this channel", + "selected_channel_invalid": "Channel type selected is invalid", + "username_no_password": "A Password must be provided with user name", + "test_unsupported": "Sorry, this channel type does not support test yet", + "channel_test_done": "An alert has been sent to this channel", + "channel_test_failed": "Failed to send a test message to this channel, please confirm that the parameters are set correctly", + "channel_test_unexpected": "An unexpected error occurred while sending a message to this channel, please try again", + "webhook_url_required": "Webhook URL is mandatory", + "slack_channel_help": "Specify channel or user, use #channel-name, @username (has to be all lowercase, no whitespace)" +} \ No newline at end of file diff --git a/frontend/public/locales/en-GB/common.json b/frontend/public/locales/en-GB/common.json new file mode 100644 index 0000000000..f167aecffc --- /dev/null +++ b/frontend/public/locales/en-GB/common.json @@ -0,0 +1,10 @@ +{ + "something_went_wrong": "Something went wrong", + "already_logged_in": "Already Logged In", + "success": "Success", + "cancel": "Cancel", + "share": "Share", + "save": "Save", + "edit": "Edit", + "logged_in": "Logged In" +} diff --git a/frontend/public/locales/en-GB/dashboard.json b/frontend/public/locales/en-GB/dashboard.json new file mode 100644 index 0000000000..7f21149511 --- /dev/null +++ b/frontend/public/locales/en-GB/dashboard.json @@ -0,0 +1,16 @@ +{ + "create_dashboard": "Create Dashboard", + "import_json": "Import JSON", + "copy_to_clipboard": "Copy To ClipBoard", + "download_json": "Download JSON", + "view_json": "View JSON", + "export_dashboard": "Export this dashboard.", + "upload_json_file": "Upload JSON file", + "paste_json_below": "Paste JSON below", + "error_upload_json": "Invalid JSON", + "load_json": "Load JSON", + "import_dashboard_by_pasting": "Import dashboard by pasting JSON or importing JSON file", + "error_loading_json": "Error loading JSON file", + "empty_json_not_allowed": "Empty JSON is not allowed", + "new_dashboard_title": "Sample Title" +} diff --git a/frontend/public/locales/en-GB/errorDetails.json b/frontend/public/locales/en-GB/errorDetails.json new file mode 100644 index 0000000000..f29f4993c8 --- /dev/null +++ b/frontend/public/locales/en-GB/errorDetails.json @@ -0,0 +1,7 @@ +{ + "see_trace_graph": "See what happened before and after this error in a trace graph", + "see_error_in_trace_graph": "See the error in trace graph", + "stack_trace": "Stacktrace", + "older": "Older", + "newer": "Newer" +} diff --git a/frontend/public/locales/en-GB/organizationsettings.json b/frontend/public/locales/en-GB/organizationsettings.json new file mode 100644 index 0000000000..74797b447b --- /dev/null +++ b/frontend/public/locales/en-GB/organizationsettings.json @@ -0,0 +1,13 @@ +{ + "display_name": "Display Name", + "signoz": "SigNoz", + "email_address": "Email address", + "name_optional": "Name (optional)", + "role": "Role", + "email_placeholder": "john@signoz.io", + "name_placeholder": "John", + "add_another_team_member": "Add another team member", + "invite_team_members": "Invite team members", + "invite_members": "Invite Members", + "pending_invites": "Pending Invites" +} diff --git a/frontend/public/locales/en-GB/routes.json b/frontend/public/locales/en-GB/routes.json new file mode 100644 index 0000000000..f3df8f6c9d --- /dev/null +++ b/frontend/public/locales/en-GB/routes.json @@ -0,0 +1,6 @@ +{ + "general": "General", + "alert_channels": "Alert Channels", + "organization_settings": "Organization Settings", + "my_settings": "My Settings" +} diff --git a/frontend/public/locales/en-GB/settings.json b/frontend/public/locales/en-GB/settings.json new file mode 100644 index 0000000000..b5041b3136 --- /dev/null +++ b/frontend/public/locales/en-GB/settings.json @@ -0,0 +1,5 @@ +{ + "current_password": "Current Password", + "new_password": "New Password", + "change_password": "Change Password" +} diff --git a/frontend/public/locales/en-GB/translation.json b/frontend/public/locales/en-GB/translation.json new file mode 100644 index 0000000000..7ad8e9a716 --- /dev/null +++ b/frontend/public/locales/en-GB/translation.json @@ -0,0 +1,28 @@ +{ + "monitor_signup": "Monitor your applications. Find what is causing issues.", + "version": "Version", + "latest_version": "Latest version", + "current_version": "Current version", + "release_notes": "Release Notes", + "read_how_to_upgrade": "Read instructions on how to upgrade", + "latest_version_signoz": "You are running the latest version of SigNoz.", + "stale_version": "You are on an older version and may be loosing on the latest features we have shipped. We recommend to upgrade to the latest version", + "oops_something_went_wrong_version": "Oops.. facing issues with fetching updated version information", + "n_a": "N/A", + "routes": { + "general": "General", + "alert_channels": "Alert Channels", + "all_errors": "All Exceptions" + }, + "settings": { + "total_retention_period": "Total Retention Period", + "move_to_s3": "Move to S3\n(should be lower than total retention period)", + "retention_success_message": "Congrats. The retention periods for {{name}} has been updated successfully.", + "retention_error_message": "There was an issue in changing the retention period for {{name}}. Please try again or reach out to support@signoz.io", + "retention_failed_message": "There was an issue in changing the retention period. Please try again or reach out to support@signoz.io", + "retention_comparison_error": "Total retention period for {{name}} can’t be lower or equal to the period after which data is moved to s3.", + "retention_null_value_error": "Retention Period for {{name}} is not set yet. Please set by choosing below", + "retention_confirmation": "Are you sure you want to change the retention period?", + "retention_confirmation_description": "This will change the amount of storage needed for saving metrics & traces." + } +} diff --git a/frontend/public/locales/en/channels.json b/frontend/public/locales/en/channels.json new file mode 100644 index 0000000000..5e670cc536 --- /dev/null +++ b/frontend/public/locales/en/channels.json @@ -0,0 +1,48 @@ +{ + "page_title_create": "New Notification Channels", + "page_title_edit": "Edit Notification Channels", + "button_save_channel": "Save", + "button_test_channel": "Test", + "button_return": "Back", + "field_channel_name": "Name", + "field_channel_type": "Type", + "field_webhook_url": "Webhook URL", + "field_slack_recipient": "Recipient", + "field_slack_title": "Title", + "field_slack_description": "Description", + "field_webhook_username": "User Name (optional)", + "field_webhook_password": "Password (optional)", + "field_pager_routing_key": "Routing Key", + "field_pager_description": "Description", + "field_pager_severity": "Severity", + "field_pager_details": "Additional Information", + "field_pager_component": "Component", + "field_pager_group": "Group", + "field_pager_class": "Class", + "field_pager_client": "Client", + "field_pager_client_url": "Client URL", + "placeholder_slack_description": "Description", + "placeholder_pager_description": "Description", + "help_pager_client": "Shows up as event source in Pagerduty", + "help_pager_client_url": "Shows up as event source link in Pagerduty", + "help_pager_class": "The class/type of the event", + "help_pager_details": "Specify a key-value format (must be a valid json)", + "help_pager_group": "A cluster or grouping of sources", + "help_pager_component": "The part or component of the affected system that is broke", + "help_pager_severity": "Severity of the incident, must be one of: must be one of the following: 'critical', 'warning', 'error' or 'info'", + "help_webhook_username": "Leave empty for bearer auth or when authentication is not necessary.", + "help_webhook_password": "Specify a password or bearer token", + "help_pager_description": "Shows up as description in pagerduty", + "channel_creation_done": "Successfully created the channel", + "channel_creation_failed": "An unexpected error occurred while creating this channel", + "channel_edit_done": "Channels Edited Successfully", + "channel_edit_failed": "An unexpected error occurred while updating this channel", + "selected_channel_invalid": "Channel type selected is invalid", + "username_no_password": "A Password must be provided with user name", + "test_unsupported": "Sorry, this channel type does not support test yet", + "channel_test_done": "An alert has been sent to this channel", + "channel_test_failed": "Failed to send a test message to this channel, please confirm that the parameters are set correctly", + "channel_test_unexpected": "An unexpected error occurred while sending a message to this channel, please try again", + "webhook_url_required": "Webhook URL is mandatory", + "slack_channel_help": "Specify channel or user, use #channel-name, @username (has to be all lowercase, no whitespace)" +} \ No newline at end of file diff --git a/frontend/public/locales/en/common.json b/frontend/public/locales/en/common.json new file mode 100644 index 0000000000..f167aecffc --- /dev/null +++ b/frontend/public/locales/en/common.json @@ -0,0 +1,10 @@ +{ + "something_went_wrong": "Something went wrong", + "already_logged_in": "Already Logged In", + "success": "Success", + "cancel": "Cancel", + "share": "Share", + "save": "Save", + "edit": "Edit", + "logged_in": "Logged In" +} diff --git a/frontend/public/locales/en/dashboard.json b/frontend/public/locales/en/dashboard.json new file mode 100644 index 0000000000..7f21149511 --- /dev/null +++ b/frontend/public/locales/en/dashboard.json @@ -0,0 +1,16 @@ +{ + "create_dashboard": "Create Dashboard", + "import_json": "Import JSON", + "copy_to_clipboard": "Copy To ClipBoard", + "download_json": "Download JSON", + "view_json": "View JSON", + "export_dashboard": "Export this dashboard.", + "upload_json_file": "Upload JSON file", + "paste_json_below": "Paste JSON below", + "error_upload_json": "Invalid JSON", + "load_json": "Load JSON", + "import_dashboard_by_pasting": "Import dashboard by pasting JSON or importing JSON file", + "error_loading_json": "Error loading JSON file", + "empty_json_not_allowed": "Empty JSON is not allowed", + "new_dashboard_title": "Sample Title" +} diff --git a/frontend/public/locales/en/errorDetails.json b/frontend/public/locales/en/errorDetails.json new file mode 100644 index 0000000000..f29f4993c8 --- /dev/null +++ b/frontend/public/locales/en/errorDetails.json @@ -0,0 +1,7 @@ +{ + "see_trace_graph": "See what happened before and after this error in a trace graph", + "see_error_in_trace_graph": "See the error in trace graph", + "stack_trace": "Stacktrace", + "older": "Older", + "newer": "Newer" +} diff --git a/frontend/public/locales/en/organizationsettings.json b/frontend/public/locales/en/organizationsettings.json new file mode 100644 index 0000000000..74797b447b --- /dev/null +++ b/frontend/public/locales/en/organizationsettings.json @@ -0,0 +1,13 @@ +{ + "display_name": "Display Name", + "signoz": "SigNoz", + "email_address": "Email address", + "name_optional": "Name (optional)", + "role": "Role", + "email_placeholder": "john@signoz.io", + "name_placeholder": "John", + "add_another_team_member": "Add another team member", + "invite_team_members": "Invite team members", + "invite_members": "Invite Members", + "pending_invites": "Pending Invites" +} diff --git a/frontend/public/locales/en/routes.json b/frontend/public/locales/en/routes.json new file mode 100644 index 0000000000..f3df8f6c9d --- /dev/null +++ b/frontend/public/locales/en/routes.json @@ -0,0 +1,6 @@ +{ + "general": "General", + "alert_channels": "Alert Channels", + "organization_settings": "Organization Settings", + "my_settings": "My Settings" +} diff --git a/frontend/public/locales/en/settings.json b/frontend/public/locales/en/settings.json new file mode 100644 index 0000000000..94a4f71407 --- /dev/null +++ b/frontend/public/locales/en/settings.json @@ -0,0 +1,6 @@ +{ + "current_password": "Current Password", + "new_password": "New Password", + "change_password": "Change Password", + "input_password": "input password" +} diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index 23330080e6..7ad8e9a716 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -11,7 +11,8 @@ "n_a": "N/A", "routes": { "general": "General", - "alert_channels": "Alert Channels" + "alert_channels": "Alert Channels", + "all_errors": "All Exceptions" }, "settings": { "total_retention_period": "Total Retention Period", diff --git a/frontend/public/signoz.svg b/frontend/public/signoz.svg index 53a3a23754..cdfe945052 100644 --- a/frontend/public/signoz.svg +++ b/frontend/public/signoz.svg @@ -1,5 +1,4 @@ - - - - - \ No newline at end of file + + + + diff --git a/frontend/src/AppRoutes/Private.tsx b/frontend/src/AppRoutes/Private.tsx new file mode 100644 index 0000000000..3e97adec58 --- /dev/null +++ b/frontend/src/AppRoutes/Private.tsx @@ -0,0 +1,164 @@ +/* eslint-disable react-hooks/exhaustive-deps */ +import { notification } from 'antd'; +import getLocalStorageApi from 'api/browser/localstorage/get'; +import loginApi from 'api/user/login'; +import Spinner from 'components/Spinner'; +import { LOCALSTORAGE } from 'constants/localStorage'; +import ROUTES from 'constants/routes'; +import history from 'lib/history'; +import React, { useEffect, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDispatch, useSelector } from 'react-redux'; +import { matchPath, Redirect, useLocation } from 'react-router-dom'; +import { Dispatch } from 'redux'; +import { AppState } from 'store/reducers'; +import { getInitialUserTokenRefreshToken } from 'store/utils'; +import AppActions from 'types/actions'; +import { UPDATE_USER_IS_FETCH } from 'types/actions/app'; +import AppReducer from 'types/reducer/app'; +import { routePermission } from 'utils/permission'; + +import routes from './routes'; +import afterLogin from './utils'; + +function PrivateRoute({ children }: PrivateRouteProps): JSX.Element { + const { pathname } = useLocation(); + + const mapRoutes = useMemo( + () => + new Map( + routes.map((e) => { + const currentPath = matchPath(pathname, { + path: e.path, + }); + return [currentPath === null ? null : 'current', e]; + }), + ), + [pathname], + ); + const { + isUserFetching, + isUserFetchingError, + isLoggedIn: isLoggedInState, + } = useSelector((state) => state.app); + + const { t } = useTranslation(['common']); + + const dispatch = useDispatch>(); + + const currentRoute = mapRoutes.get('current'); + + const navigateToLoginIfNotLoggedIn = (isLoggedIn = isLoggedInState): void => { + dispatch({ + type: UPDATE_USER_IS_FETCH, + payload: { + isUserFetching: false, + }, + }); + + if (!isLoggedIn) { + history.push(ROUTES.LOGIN); + } + }; + + // eslint-disable-next-line sonarjs/cognitive-complexity + useEffect(() => { + (async (): Promise => { + try { + const isLocalStorageLoggedIn = + getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true'; + if (currentRoute) { + const { isPrivate, key } = currentRoute; + + if (isPrivate) { + const localStorageUserAuthToken = getInitialUserTokenRefreshToken(); + + if ( + localStorageUserAuthToken && + localStorageUserAuthToken.refreshJwt && + isUserFetching + ) { + // localstorage token is present + const { refreshJwt } = localStorageUserAuthToken; + + // renew web access token + const response = await loginApi({ + refreshToken: refreshJwt, + }); + + if (response.statusCode === 200) { + const route = routePermission[key]; + + // get all resource and put it over redux + const userResponse = await afterLogin( + response.payload.userId, + response.payload.accessJwt, + response.payload.refreshJwt, + ); + + if ( + userResponse && + route.find((e) => e === userResponse.payload.role) === undefined + ) { + history.push(ROUTES.UN_AUTHORIZED); + } + } else { + history.push(ROUTES.SOMETHING_WENT_WRONG); + + notification.error({ + message: response.error || t('something_went_wrong'), + }); + } + } else { + // user does have localstorage values + navigateToLoginIfNotLoggedIn(isLocalStorageLoggedIn); + } + } else { + // no need to fetch the user and make user fetching false + + if (getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true') { + history.push(ROUTES.APPLICATION); + } + dispatch({ + type: UPDATE_USER_IS_FETCH, + payload: { + isUserFetching: false, + }, + }); + } + } else if (pathname === ROUTES.HOME_PAGE) { + // routing to application page over root page + if (isLoggedInState) { + history.push(ROUTES.APPLICATION); + } else { + navigateToLoginIfNotLoggedIn(); + } + } else { + // not found + navigateToLoginIfNotLoggedIn(isLocalStorageLoggedIn); + } + } catch (error) { + // something went wrong + history.push(ROUTES.SOMETHING_WENT_WRONG); + } + })(); + }, [dispatch, isLoggedInState, currentRoute]); + + if (isUserFetchingError) { + return ; + } + + if (isUserFetching) { + return ; + } + + // NOTE: disabling this rule as there is no need to have div + // eslint-disable-next-line react/jsx-no-useless-fragment + return <>{children}>; +} + +interface PrivateRouteProps { + children: React.ReactChild; +} + +export default PrivateRoute; diff --git a/frontend/src/AppRoutes/index.tsx b/frontend/src/AppRoutes/index.tsx index 018708c1d0..968ac0b2ff 100644 --- a/frontend/src/AppRoutes/index.tsx +++ b/frontend/src/AppRoutes/index.tsx @@ -1,42 +1,36 @@ import NotFound from 'components/NotFound'; import Spinner from 'components/Spinner'; -import ROUTES from 'constants/routes'; import AppLayout from 'container/AppLayout'; import history from 'lib/history'; import React, { Suspense } from 'react'; -import { useSelector } from 'react-redux'; -import { Redirect, Route, Router, Switch } from 'react-router-dom'; -import { AppState } from 'store/reducers'; -import AppReducer from 'types/reducer/app'; +import { Route, Router, Switch } from 'react-router-dom'; +import PrivateRoute from './Private'; import routes from './routes'; function App(): JSX.Element { - const { isLoggedIn } = useSelector((state) => state.app); - return ( - - }> - - {routes.map(({ path, component, exact }) => ( - - ))} - - isLoggedIn ? ( - - ) : ( - - ) - } - /> - - - - + + + }> + + {routes.map(({ path, component, exact }) => { + return ( + + ); + })} + + + + + + ); } diff --git a/frontend/src/AppRoutes/pageComponents.ts b/frontend/src/AppRoutes/pageComponents.ts index 25d4dc5886..fd59d0bc7b 100644 --- a/frontend/src/AppRoutes/pageComponents.ts +++ b/frontend/src/AppRoutes/pageComponents.ts @@ -83,9 +83,44 @@ export const EditAlertChannelsAlerts = Loadable( ); export const AllAlertChannels = Loadable( - () => import(/* webpackChunkName: "All Channels" */ 'pages/AllAlertChannels'), + () => import(/* webpackChunkName: "All Channels" */ 'pages/Settings'), +); + +export const AllErrors = Loadable( + /* webpackChunkName: "All Exceptions" */ () => import('pages/AllErrors'), +); + +export const ErrorDetails = Loadable( + () => import(/* webpackChunkName: "Error Details" */ 'pages/ErrorDetails'), ); export const StatusPage = Loadable( () => import(/* webpackChunkName: "All Status" */ 'pages/Status'), ); + +export const OrganizationSettings = Loadable( + () => import(/* webpackChunkName: "All Settings" */ 'pages/Settings'), +); + +export const MySettings = Loadable( + () => import(/* webpackChunkName: "All MySettings" */ 'pages/MySettings'), +); + +export const Login = Loadable( + () => import(/* webpackChunkName: "Login" */ 'pages/Login'), +); + +export const UnAuthorized = Loadable( + () => import(/* webpackChunkName: "UnAuthorized" */ 'pages/UnAuthorized'), +); + +export const PasswordReset = Loadable( + () => import(/* webpackChunkName: "ResetPassword" */ 'pages/ResetPassword'), +); + +export const SomethingWentWrong = Loadable( + () => + import( + /* webpackChunkName: "SomethingWentWrong" */ 'pages/SomethingWentWrong' + ), +); diff --git a/frontend/src/AppRoutes/routes.ts b/frontend/src/AppRoutes/routes.ts index 10f5a1997a..5958d93b11 100644 --- a/frontend/src/AppRoutes/routes.ts +++ b/frontend/src/AppRoutes/routes.ts @@ -4,22 +4,30 @@ import { RouteProps } from 'react-router-dom'; import { AllAlertChannels, + AllErrors, CreateAlertChannelAlerts, CreateNewAlerts, DashboardPage, EditAlertChannelsAlerts, EditRulesPage, + ErrorDetails, InstrumentationPage, ListAllALertsPage, + Login, + MySettings, NewDashboardPage, + OrganizationSettings, + PasswordReset, ServiceMapPage, ServiceMetricsPage, ServicesTablePage, SettingsPage, SignupPage, + SomethingWentWrong, StatusPage, TraceDetail, TraceFilter, + UnAuthorized, UsageExplorerPage, } from './pageComponents'; @@ -28,104 +36,199 @@ const routes: AppRoutes[] = [ component: SignupPage, path: ROUTES.SIGN_UP, exact: true, + isPrivate: false, + key: 'SIGN_UP', }, { component: ServicesTablePage, path: ROUTES.APPLICATION, exact: true, + isPrivate: true, + key: 'APPLICATION', }, { path: ROUTES.SERVICE_METRICS, exact: true, component: ServiceMetricsPage, + isPrivate: true, + key: 'SERVICE_METRICS', }, { path: ROUTES.SERVICE_MAP, component: ServiceMapPage, + isPrivate: true, exact: true, + key: 'SERVICE_MAP', }, { path: ROUTES.TRACE_DETAIL, exact: true, component: TraceDetail, + isPrivate: true, + key: 'TRACE_DETAIL', }, { path: ROUTES.SETTINGS, exact: true, component: SettingsPage, + isPrivate: true, + key: 'SETTINGS', }, { path: ROUTES.USAGE_EXPLORER, exact: true, component: UsageExplorerPage, + isPrivate: true, + key: 'USAGE_EXPLORER', }, { path: ROUTES.INSTRUMENTATION, exact: true, component: InstrumentationPage, + isPrivate: true, + key: 'INSTRUMENTATION', }, { path: ROUTES.ALL_DASHBOARD, exact: true, component: DashboardPage, + isPrivate: true, + key: 'ALL_DASHBOARD', }, { path: ROUTES.DASHBOARD, exact: true, component: NewDashboardPage, + isPrivate: true, + key: 'DASHBOARD', }, { path: ROUTES.DASHBOARD_WIDGET, exact: true, component: DashboardWidget, + isPrivate: true, + key: 'DASHBOARD_WIDGET', }, { path: ROUTES.EDIT_ALERTS, exact: true, component: EditRulesPage, + isPrivate: true, + key: 'EDIT_ALERTS', }, { path: ROUTES.LIST_ALL_ALERT, exact: true, component: ListAllALertsPage, + isPrivate: true, + key: 'LIST_ALL_ALERT', }, { path: ROUTES.ALERTS_NEW, exact: true, component: CreateNewAlerts, + isPrivate: true, + key: 'ALERTS_NEW', }, { path: ROUTES.TRACE, exact: true, component: TraceFilter, + isPrivate: true, + key: 'TRACE', }, { path: ROUTES.CHANNELS_NEW, exact: true, component: CreateAlertChannelAlerts, + isPrivate: true, + key: 'CHANNELS_NEW', }, { path: ROUTES.CHANNELS_EDIT, exact: true, component: EditAlertChannelsAlerts, + isPrivate: true, + key: 'CHANNELS_EDIT', }, { path: ROUTES.ALL_CHANNELS, exact: true, component: AllAlertChannels, + isPrivate: true, + key: 'ALL_CHANNELS', + }, + { + path: ROUTES.ALL_ERROR, + exact: true, + isPrivate: true, + component: AllErrors, + key: 'ALL_ERROR', + }, + { + path: ROUTES.ERROR_DETAIL, + exact: true, + component: ErrorDetails, + isPrivate: true, + key: 'ERROR_DETAIL', }, { path: ROUTES.VERSION, exact: true, component: StatusPage, + isPrivate: true, + key: 'VERSION', + }, + { + path: ROUTES.ORG_SETTINGS, + exact: true, + component: OrganizationSettings, + isPrivate: true, + key: 'ORG_SETTINGS', + }, + { + path: ROUTES.MY_SETTINGS, + exact: true, + component: MySettings, + isPrivate: true, + key: 'MY_SETTINGS', + }, + { + path: ROUTES.LOGIN, + exact: true, + component: Login, + isPrivate: false, + key: 'LOGIN', + }, + { + path: ROUTES.UN_AUTHORIZED, + exact: true, + component: UnAuthorized, + key: 'UN_AUTHORIZED', + isPrivate: true, + }, + { + path: ROUTES.PASSWORD_RESET, + exact: true, + component: PasswordReset, + key: 'PASSWORD_RESET', + isPrivate: false, + }, + { + path: ROUTES.SOMETHING_WENT_WRONG, + exact: true, + component: SomethingWentWrong, + key: 'SOMETHING_WENT_WRONG', + isPrivate: false, }, ]; -interface AppRoutes { +export interface AppRoutes { component: RouteProps['component']; path: RouteProps['path']; exact: RouteProps['exact']; - isPrivate?: boolean; + isPrivate: boolean; + key: keyof typeof ROUTES; } export default routes; diff --git a/frontend/src/AppRoutes/utils.ts b/frontend/src/AppRoutes/utils.ts new file mode 100644 index 0000000000..698629d325 --- /dev/null +++ b/frontend/src/AppRoutes/utils.ts @@ -0,0 +1,91 @@ +import getLocalStorageApi from 'api/browser/localstorage/get'; +import setLocalStorageApi from 'api/browser/localstorage/set'; +import getUserApi from 'api/user/getUser'; +import { Logout } from 'api/utils'; +import { LOCALSTORAGE } from 'constants/localStorage'; +import store from 'store'; +import AppActions from 'types/actions'; +import { + LOGGED_IN, + UPDATE_USER, + UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN, + UPDATE_USER_IS_FETCH, +} from 'types/actions/app'; +import { SuccessResponse } from 'types/api'; +import { PayloadProps } from 'types/api/user/getUser'; + +const afterLogin = async ( + userId: string, + authToken: string, + refreshToken: string, +): Promise | undefined> => { + setLocalStorageApi(LOCALSTORAGE.AUTH_TOKEN, authToken); + setLocalStorageApi(LOCALSTORAGE.REFRESH_AUTH_TOKEN, refreshToken); + + store.dispatch({ + type: UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN, + payload: { + accessJwt: authToken, + refreshJwt: refreshToken, + }, + }); + + const [getUserResponse] = await Promise.all([ + getUserApi({ + userId, + token: authToken, + }), + ]); + + if (getUserResponse.statusCode === 200 && getUserResponse.payload) { + store.dispatch({ + type: LOGGED_IN, + payload: { + isLoggedIn: true, + }, + }); + + const { payload } = getUserResponse; + + store.dispatch({ + type: UPDATE_USER, + payload: { + ROLE: payload.role, + email: payload.email, + name: payload.name, + orgName: payload.organization, + profilePictureURL: payload.profilePictureURL, + userId: payload.id, + orgId: payload.orgId, + }, + }); + + const isLoggedInLocalStorage = getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN); + + if (isLoggedInLocalStorage === null) { + setLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN, 'true'); + } + + store.dispatch({ + type: UPDATE_USER_IS_FETCH, + payload: { + isUserFetching: false, + }, + }); + + return getUserResponse; + } + + store.dispatch({ + type: UPDATE_USER_IS_FETCH, + payload: { + isUserFetching: false, + }, + }); + + Logout(); + + return undefined; +}; + +export default afterLogin; diff --git a/frontend/src/ReactI18/index.tsx b/frontend/src/ReactI18/index.tsx index f452ed227b..3b37751caf 100644 --- a/frontend/src/ReactI18/index.tsx +++ b/frontend/src/ReactI18/index.tsx @@ -12,7 +12,7 @@ i18n .use(initReactI18next) // init i18next .init({ - debug: true, + debug: false, fallbackLng: 'en', interpolation: { escapeValue: false, // not needed for react as it escapes by default diff --git a/frontend/src/api/ErrorResponseHandler.ts b/frontend/src/api/ErrorResponseHandler.ts index 9356b7ee77..060b93493f 100644 --- a/frontend/src/api/ErrorResponseHandler.ts +++ b/frontend/src/api/ErrorResponseHandler.ts @@ -21,10 +21,15 @@ export function ErrorResponseHandler(error: AxiosError): ErrorResponse { }; } + const { errors, error } = data; + + const errorMessage = + Array.isArray(errors) && errors.length >= 1 ? errors[0].msg : error; + return { statusCode, payload: null, - error: data.error, + error: errorMessage, message: null, }; } diff --git a/frontend/src/api/apiV1.ts b/frontend/src/api/apiV1.ts index 22054bf229..5145443b2a 100644 --- a/frontend/src/api/apiV1.ts +++ b/frontend/src/api/apiV1.ts @@ -1,4 +1,6 @@ const apiV1 = '/api/v1/'; -export const apiV2 = '/api/alertmanager'; + +export const apiV2 = '/api/v2/'; +export const apiAlertManager = '/api/alertmanager'; export default apiV1; diff --git a/frontend/src/api/channels/createPager.ts b/frontend/src/api/channels/createPager.ts new file mode 100644 index 0000000000..2747768cf1 --- /dev/null +++ b/frontend/src/api/channels/createPager.ts @@ -0,0 +1,42 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/channels/createPager'; + +const create = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.post('/channels', { + name: props.name, + pagerduty_configs: [ + { + send_resolved: true, + routing_key: props.routing_key, + client: props.client, + client_url: props.client_url, + description: props.description, + severity: props.severity, + class: props.class, + component: props.component, + group: props.group, + details: { + ...props.detailsArray, + }, + }, + ], + }); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default create; diff --git a/frontend/src/api/channels/editPager.ts b/frontend/src/api/channels/editPager.ts new file mode 100644 index 0000000000..a31d73dcdb --- /dev/null +++ b/frontend/src/api/channels/editPager.ts @@ -0,0 +1,42 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/channels/editPager'; + +const editPager = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.put(`/channels/${props.id}`, { + name: props.name, + pagerduty_configs: [ + { + send_resolved: true, + routing_key: props.routing_key, + client: props.client, + client_url: props.client_url, + description: props.description, + severity: props.severity, + class: props.class, + component: props.component, + group: props.group, + details: { + ...props.detailsArray, + }, + }, + ], + }); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default editPager; diff --git a/frontend/src/api/channels/testPager.ts b/frontend/src/api/channels/testPager.ts new file mode 100644 index 0000000000..717404649a --- /dev/null +++ b/frontend/src/api/channels/testPager.ts @@ -0,0 +1,42 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/channels/createPager'; + +const testPager = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.post('/testChannel', { + name: props.name, + pagerduty_configs: [ + { + send_resolved: true, + routing_key: props.routing_key, + client: props.client, + client_url: props.client_url, + description: props.description, + severity: props.severity, + class: props.class, + component: props.component, + group: props.group, + details: { + ...props.detailsArray, + }, + }, + ], + }); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default testPager; diff --git a/frontend/src/api/channels/testSlack.ts b/frontend/src/api/channels/testSlack.ts new file mode 100644 index 0000000000..a2b4b1f40a --- /dev/null +++ b/frontend/src/api/channels/testSlack.ts @@ -0,0 +1,35 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/channels/createSlack'; + +const testSlack = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.post('/testChannel', { + name: props.name, + slack_configs: [ + { + send_resolved: true, + api_url: props.api_url, + channel: props.channel, + title: props.title, + text: props.text, + }, + ], + }); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default testSlack; diff --git a/frontend/src/api/channels/testWebhook.ts b/frontend/src/api/channels/testWebhook.ts new file mode 100644 index 0000000000..4b915e9a3a --- /dev/null +++ b/frontend/src/api/channels/testWebhook.ts @@ -0,0 +1,51 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/channels/createWebhook'; + +const testWebhook = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + let httpConfig = {}; + + if (props.username !== '' && props.password !== '') { + httpConfig = { + basic_auth: { + username: props.username, + password: props.password, + }, + }; + } else if (props.username === '' && props.password !== '') { + httpConfig = { + authorization: { + type: 'bearer', + credentials: props.password, + }, + }; + } + + const response = await axios.post('/testChannel', { + name: props.name, + webhook_configs: [ + { + send_resolved: true, + url: props.api_url, + http_config: httpConfig, + }, + ], + }); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default testWebhook; diff --git a/frontend/src/api/dashboard/update.ts b/frontend/src/api/dashboard/update.ts index b22689c0ed..37341524f8 100644 --- a/frontend/src/api/dashboard/update.ts +++ b/frontend/src/api/dashboard/update.ts @@ -9,7 +9,7 @@ const update = async ( ): Promise | ErrorResponse> => { try { const response = await axios.put(`/dashboards/${props.uuid}`, { - ...props, + ...props.data, }); return { diff --git a/frontend/src/api/errors/getAll.ts b/frontend/src/api/errors/getAll.ts new file mode 100644 index 0000000000..dcd8aa8e73 --- /dev/null +++ b/frontend/src/api/errors/getAll.ts @@ -0,0 +1,30 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import createQueryParams from 'lib/createQueryParams'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/errors/getAll'; + +const getAll = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.get( + `/errors?${createQueryParams({ + start: props.start.toString(), + end: props.end.toString(), + })}`, + ); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default getAll; diff --git a/frontend/src/api/errors/getByErrorTypeAndService.ts b/frontend/src/api/errors/getByErrorTypeAndService.ts new file mode 100644 index 0000000000..6a2c6964d9 --- /dev/null +++ b/frontend/src/api/errors/getByErrorTypeAndService.ts @@ -0,0 +1,32 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import createQueryParams from 'lib/createQueryParams'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/errors/getByErrorTypeAndService'; + +const getByErrorType = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.get( + `/errorWithType?${createQueryParams({ + start: props.start.toString(), + end: props.end.toString(), + serviceName: props.serviceName, + errorType: props.errorType, + })}`, + ); + + return { + statusCode: 200, + error: null, + message: response.data.message, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default getByErrorType; diff --git a/frontend/src/api/errors/getById.ts b/frontend/src/api/errors/getById.ts new file mode 100644 index 0000000000..3ab7c4aa60 --- /dev/null +++ b/frontend/src/api/errors/getById.ts @@ -0,0 +1,31 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import createQueryParams from 'lib/createQueryParams'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/errors/getById'; + +const getById = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.get( + `/errorWithId?${createQueryParams({ + start: props.start.toString(), + end: props.end.toString(), + errorId: props.errorId, + })}`, + ); + + return { + statusCode: 200, + error: null, + message: response.data.message, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default getById; diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index feaac180e4..82f4bdc010 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -1,14 +1,113 @@ -import axios from 'axios'; +/* eslint-disable sonarjs/cognitive-complexity */ +/* eslint-disable no-param-reassign */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import getLocalStorageApi from 'api/browser/localstorage/get'; +import loginApi from 'api/user/login'; +import afterLogin from 'AppRoutes/utils'; +import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; import { ENVIRONMENT } from 'constants/env'; +import { LOCALSTORAGE } from 'constants/localStorage'; +import store from 'store'; -import apiV1, { apiV2 } from './apiV1'; +import apiV1, { apiAlertManager, apiV2 } from './apiV1'; +import { Logout } from './utils'; -export default axios.create({ +const interceptorsResponse = ( + value: AxiosResponse, +): Promise> => Promise.resolve(value); + +const interceptorsRequestResponse = ( + value: AxiosRequestConfig, +): AxiosRequestConfig => { + const token = + store.getState().app.user?.accessJwt || + getLocalStorageApi(LOCALSTORAGE.AUTH_TOKEN) || + ''; + + value.headers.Authorization = token ? `Bearer ${token}` : ''; + + return value; +}; + +const interceptorRejected = async ( + value: AxiosResponse, +): Promise> => { + try { + if (axios.isAxiosError(value) && value.response) { + const { response } = value; + // reject the refresh token error + if (response.status === 401 && response.config.url !== '/login') { + const response = await loginApi({ + refreshToken: store.getState().app.user?.refreshJwt, + }); + + if (response.statusCode === 200) { + const user = await afterLogin( + response.payload.userId, + response.payload.accessJwt, + response.payload.refreshJwt, + ); + + if (user) { + const reResponse = await axios( + `${value.config.baseURL}${value.config.url?.substring(1)}`, + { + method: value.config.method, + headers: { + ...value.config.headers, + Authorization: `Bearer ${response.payload.accessJwt}`, + }, + data: { + ...JSON.parse(value.config.data || '{}'), + }, + }, + ); + + if (reResponse.status === 200) { + return await Promise.resolve(reResponse); + } + Logout(); + + return await Promise.reject(reResponse); + } + Logout(); + + return await Promise.reject(value); + } + Logout(); + } + + // when refresh token is expired + if (response.status === 401 && response.config.url === '/login') { + Logout(); + } + } + return await Promise.reject(value); + } catch (error) { + return await Promise.reject(error); + } +}; + +const instance = axios.create({ baseURL: `${ENVIRONMENT.baseURL}${apiV1}`, }); +instance.interceptors.response.use(interceptorsResponse, interceptorRejected); +instance.interceptors.request.use(interceptorsRequestResponse); + export const AxiosAlertManagerInstance = axios.create({ + baseURL: `${ENVIRONMENT.baseURL}${apiAlertManager}`, +}); + +export const ApiV2Instance = axios.create({ baseURL: `${ENVIRONMENT.baseURL}${apiV2}`, }); +AxiosAlertManagerInstance.interceptors.response.use( + interceptorsResponse, + interceptorRejected, +); +AxiosAlertManagerInstance.interceptors.request.use(interceptorsRequestResponse); + export { apiV1 }; +export default instance; diff --git a/frontend/src/api/metrics/getResourceAttributes.ts b/frontend/src/api/metrics/getResourceAttributes.ts new file mode 100644 index 0000000000..5be45af6f1 --- /dev/null +++ b/frontend/src/api/metrics/getResourceAttributes.ts @@ -0,0 +1,47 @@ +import { ApiV2Instance as axios } from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { + TagKeysPayloadProps, + TagValueProps, + TagValuesPayloadProps, +} from 'types/api/metrics/getResourceAttributes'; + +export const getResourceAttributesTagKeys = async (): Promise< + SuccessResponse | ErrorResponse +> => { + try { + const response = await axios.get( + '/metrics/autocomplete/tagKey?metricName=signoz_calls_total&match=resource_', + ); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export const getResourceAttributesTagValues = async ( + props: TagValueProps, +): Promise | ErrorResponse> => { + try { + const response = await axios.get( + `/metrics/autocomplete/tagValue?metricName=signoz_calls_total&tagKey=${props}`, + ); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; diff --git a/frontend/src/api/metrics/getService.ts b/frontend/src/api/metrics/getService.ts index 29909b2905..d3bb27c741 100644 --- a/frontend/src/api/metrics/getService.ts +++ b/frontend/src/api/metrics/getService.ts @@ -8,9 +8,11 @@ const getService = async ( props: Props, ): Promise | ErrorResponse> => { try { - const response = await axios.get( - `/services?&start=${props.start}&end=${props.end}`, - ); + const response = await axios.post(`/services`, { + start: `${props.start}`, + end: `${props.end}`, + tags: props.selectedTags, + }); return { statusCode: 200, diff --git a/frontend/src/api/metrics/getServiceOverview.ts b/frontend/src/api/metrics/getServiceOverview.ts index 3ceb794e0e..ea0ddb4062 100644 --- a/frontend/src/api/metrics/getServiceOverview.ts +++ b/frontend/src/api/metrics/getServiceOverview.ts @@ -8,9 +8,13 @@ const getServiceOverview = async ( props: Props, ): Promise | ErrorResponse> => { try { - const response = await axios.get( - `/service/overview?&start=${props.start}&end=${props.end}&service=${props.service}&step=${props.step}`, - ); + const response = await axios.post(`/service/overview`, { + start: `${props.start}`, + end: `${props.end}`, + service: props.service, + step: props.step, + tags: props.selectedTags, + }); return { statusCode: 200, diff --git a/frontend/src/api/metrics/getTopEndPoints.ts b/frontend/src/api/metrics/getTopEndPoints.ts index a2973d1866..db78aae9e3 100644 --- a/frontend/src/api/metrics/getTopEndPoints.ts +++ b/frontend/src/api/metrics/getTopEndPoints.ts @@ -8,9 +8,12 @@ const getTopEndPoints = async ( props: Props, ): Promise | ErrorResponse> => { try { - const response = await axios.get( - `/service/top_endpoints?&start=${props.start}&end=${props.end}&service=${props.service}`, - ); + const response = await axios.post(`/service/top_endpoints`, { + start: `${props.start}`, + end: `${props.end}`, + service: props.service, + tags: props.selectedTags, + }); return { statusCode: 200, diff --git a/frontend/src/api/trace/getSpansAggregate.ts b/frontend/src/api/trace/getSpansAggregate.ts index ad18cb2143..077577febf 100644 --- a/frontend/src/api/trace/getSpansAggregate.ts +++ b/frontend/src/api/trace/getSpansAggregate.ts @@ -16,6 +16,7 @@ const getSpanAggregate = async ( limit: props.limit, offset: props.offset, order: props.order, + orderParam: props.orderParam, }; const exclude: TraceFilterEnum[] = []; diff --git a/frontend/src/api/trace/getTagFilter.ts b/frontend/src/api/trace/getTagFilter.ts index 746765c4bf..e4cadb710c 100644 --- a/frontend/src/api/trace/getTagFilter.ts +++ b/frontend/src/api/trace/getTagFilter.ts @@ -4,6 +4,7 @@ import { AxiosError } from 'axios'; import { omitBy } from 'lodash-es'; import { ErrorResponse, SuccessResponse } from 'types/api'; import { PayloadProps, Props } from 'types/api/trace/getTagFilters'; +import { TraceFilterEnum } from 'types/reducer/trace'; const getTagFilters = async ( props: Props, @@ -12,6 +13,14 @@ const getTagFilters = async ( const duration = omitBy(props.other, (_, key) => !key.startsWith('duration')) || []; + const exclude: TraceFilterEnum[] = []; + + props.isFilterExclude.forEach((value, key) => { + if (value) { + exclude.push(key); + } + }); + const nonDuration = omitBy(props.other, (_, key) => key.startsWith('duration'), ); @@ -22,6 +31,7 @@ const getTagFilters = async ( ...nonDuration, maxDuration: String((duration.duration || [])[0] || ''), minDuration: String((duration.duration || [])[1] || ''), + exclude, }); return { diff --git a/frontend/src/api/user/changeMyPassword.ts b/frontend/src/api/user/changeMyPassword.ts new file mode 100644 index 0000000000..cdca2cd6bb --- /dev/null +++ b/frontend/src/api/user/changeMyPassword.ts @@ -0,0 +1,26 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/user/changeMyPassword'; + +const changeMyPassword = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.post(`/changePassword/${props.userId}`, { + ...props, + }); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default changeMyPassword; diff --git a/frontend/src/api/user/deleteInvite.ts b/frontend/src/api/user/deleteInvite.ts new file mode 100644 index 0000000000..16233ef97f --- /dev/null +++ b/frontend/src/api/user/deleteInvite.ts @@ -0,0 +1,24 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/user/deleteInvite'; + +const deleteInvite = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.delete(`/invite/${props.email}`); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default deleteInvite; diff --git a/frontend/src/api/user/deleteUser.ts b/frontend/src/api/user/deleteUser.ts new file mode 100644 index 0000000000..4eb2694782 --- /dev/null +++ b/frontend/src/api/user/deleteUser.ts @@ -0,0 +1,24 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/user/deleteUser'; + +const deleteUser = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.delete(`/user/${props.userId}`); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default deleteUser; diff --git a/frontend/src/api/user/editOrg.ts b/frontend/src/api/user/editOrg.ts new file mode 100644 index 0000000000..da980acea0 --- /dev/null +++ b/frontend/src/api/user/editOrg.ts @@ -0,0 +1,28 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/user/editOrg'; + +const editOrg = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.put(`/org/${props.orgId}`, { + name: props.name, + isAnonymous: props.isAnonymous, + hasOptedUpdates: props.hasOptedUpdates, + }); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default editOrg; diff --git a/frontend/src/api/user/editUser.ts b/frontend/src/api/user/editUser.ts new file mode 100644 index 0000000000..88f7c40a25 --- /dev/null +++ b/frontend/src/api/user/editUser.ts @@ -0,0 +1,26 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/user/editUser'; + +const editUser = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.put(`/user/${props.userId}`, { + Name: props.name, + }); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default editUser; diff --git a/frontend/src/api/user/getInviteDetails.ts b/frontend/src/api/user/getInviteDetails.ts new file mode 100644 index 0000000000..b1e4ad7ae5 --- /dev/null +++ b/frontend/src/api/user/getInviteDetails.ts @@ -0,0 +1,24 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/user/getInviteDetails'; + +const getInviteDetails = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.get(`/invite/${props.inviteId}`); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default getInviteDetails; diff --git a/frontend/src/api/user/getOrgUser.ts b/frontend/src/api/user/getOrgUser.ts new file mode 100644 index 0000000000..8956adc1ba --- /dev/null +++ b/frontend/src/api/user/getOrgUser.ts @@ -0,0 +1,24 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/user/getOrgMembers'; + +const getOrgUser = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.get(`/orgUsers/${props.orgId}`); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default getOrgUser; diff --git a/frontend/src/api/user/getOrganization.ts b/frontend/src/api/user/getOrganization.ts new file mode 100644 index 0000000000..dfda5e44e6 --- /dev/null +++ b/frontend/src/api/user/getOrganization.ts @@ -0,0 +1,28 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps } from 'types/api/user/getOrganization'; + +const getOrganization = async ( + token?: string, +): Promise | ErrorResponse> => { + try { + const response = await axios.get(`/org`, { + headers: { + Authorization: `bearer ${token}`, + }, + }); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default getOrganization; diff --git a/frontend/src/api/user/getPendingInvites.ts b/frontend/src/api/user/getPendingInvites.ts new file mode 100644 index 0000000000..947b7bf755 --- /dev/null +++ b/frontend/src/api/user/getPendingInvites.ts @@ -0,0 +1,24 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps } from 'types/api/user/getPendingInvites'; + +const getPendingInvites = async (): Promise< + SuccessResponse | ErrorResponse +> => { + try { + const response = await axios.get(`/invite`); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default getPendingInvites; diff --git a/frontend/src/api/user/getResetPasswordToken.ts b/frontend/src/api/user/getResetPasswordToken.ts new file mode 100644 index 0000000000..845826ed70 --- /dev/null +++ b/frontend/src/api/user/getResetPasswordToken.ts @@ -0,0 +1,24 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/user/getResetPasswordToken'; + +const getResetPasswordToken = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.get(`/getResetPasswordToken/${props.userId}`); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default getResetPasswordToken; diff --git a/frontend/src/api/user/getRoles.ts b/frontend/src/api/user/getRoles.ts new file mode 100644 index 0000000000..0602a0aa63 --- /dev/null +++ b/frontend/src/api/user/getRoles.ts @@ -0,0 +1,28 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/user/getUserRole'; + +const getRoles = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.get(`/rbac/role/${props.userId}`, { + headers: { + Authorization: `bearer ${props.token}`, + }, + }); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default getRoles; diff --git a/frontend/src/api/user/getUser.ts b/frontend/src/api/user/getUser.ts new file mode 100644 index 0000000000..6bedb78d2e --- /dev/null +++ b/frontend/src/api/user/getUser.ts @@ -0,0 +1,28 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/user/getUser'; + +const getUser = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.get(`/user/${props.userId}`, { + headers: { + Authorization: `bearer ${props.token}`, + }, + }); + + return { + statusCode: 200, + error: null, + message: 'Success', + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default getUser; diff --git a/frontend/src/api/user/login.ts b/frontend/src/api/user/login.ts new file mode 100644 index 0000000000..4eff88337b --- /dev/null +++ b/frontend/src/api/user/login.ts @@ -0,0 +1,26 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/user/login'; + +const login = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.post(`/login`, { + ...props, + }); + + return { + statusCode: 200, + error: null, + message: response.statusText, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default login; diff --git a/frontend/src/api/user/resetPassword.ts b/frontend/src/api/user/resetPassword.ts new file mode 100644 index 0000000000..eb6d2752c7 --- /dev/null +++ b/frontend/src/api/user/resetPassword.ts @@ -0,0 +1,26 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/user/resetPassword'; + +const resetPassword = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.post(`/resetPassword`, { + ...props, + }); + + return { + statusCode: 200, + error: null, + message: response.statusText, + payload: response.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default resetPassword; diff --git a/frontend/src/api/user/setPreference.ts b/frontend/src/api/user/sendInvite.ts similarity index 71% rename from frontend/src/api/user/setPreference.ts rename to frontend/src/api/user/sendInvite.ts index de8e309b65..9835588907 100644 --- a/frontend/src/api/user/setPreference.ts +++ b/frontend/src/api/user/sendInvite.ts @@ -2,13 +2,13 @@ import axios from 'api'; import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; import { AxiosError } from 'axios'; import { ErrorResponse, SuccessResponse } from 'types/api'; -import { PayloadProps, Props } from 'types/api/user/setUserPreference'; +import { PayloadProps, Props } from 'types/api/user/setInvite'; -const setPreference = async ( +const sendInvite = async ( props: Props, ): Promise | ErrorResponse> => { try { - const response = await axios.post(`/userPreferences`, { + const response = await axios.post(`/invite`, { ...props, }); @@ -23,4 +23,4 @@ const setPreference = async ( } }; -export default setPreference; +export default sendInvite; diff --git a/frontend/src/api/user/signup.ts b/frontend/src/api/user/signup.ts index 8778b5c037..9d7ff78fa4 100644 --- a/frontend/src/api/user/signup.ts +++ b/frontend/src/api/user/signup.ts @@ -6,9 +6,9 @@ import { Props } from 'types/api/user/signup'; const signup = async ( props: Props, -): Promise | ErrorResponse> => { +): Promise | ErrorResponse> => { try { - const response = await axios.post(`/user`, { + const response = await axios.post(`/register`, { ...props, }); diff --git a/frontend/src/api/user/updateRole.ts b/frontend/src/api/user/updateRole.ts new file mode 100644 index 0000000000..5d82a3d991 --- /dev/null +++ b/frontend/src/api/user/updateRole.ts @@ -0,0 +1,26 @@ +import axios from 'api'; +import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; +import { AxiosError } from 'axios'; +import { ErrorResponse, SuccessResponse } from 'types/api'; +import { PayloadProps, Props } from 'types/api/user/updateRole'; + +const updateRole = async ( + props: Props, +): Promise | ErrorResponse> => { + try { + const response = await axios.put(`/rbac/role/${props.userId}`, { + group_name: props.group_name, + }); + + return { + statusCode: 200, + error: null, + message: response.data.status, + payload: response.data.data, + }; + } catch (error) { + return ErrorResponseHandler(error as AxiosError); + } +}; + +export default updateRole; diff --git a/frontend/src/api/utils.ts b/frontend/src/api/utils.ts new file mode 100644 index 0000000000..56867927a8 --- /dev/null +++ b/frontend/src/api/utils.ts @@ -0,0 +1,55 @@ +import deleteLocalStorageKey from 'api/browser/localstorage/remove'; +import { LOCALSTORAGE } from 'constants/localStorage'; +import ROUTES from 'constants/routes'; +import history from 'lib/history'; +import store from 'store'; +import { + LOGGED_IN, + UPDATE_USER, + UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN, + UPDATE_USER_ORG_ROLE, +} from 'types/actions/app'; + +export const Logout = (): void => { + deleteLocalStorageKey(LOCALSTORAGE.AUTH_TOKEN); + deleteLocalStorageKey(LOCALSTORAGE.IS_LOGGED_IN); + deleteLocalStorageKey(LOCALSTORAGE.REFRESH_AUTH_TOKEN); + + store.dispatch({ + type: LOGGED_IN, + payload: { + isLoggedIn: false, + }, + }); + + store.dispatch({ + type: UPDATE_USER_ORG_ROLE, + payload: { + org: null, + role: null, + }, + }); + + store.dispatch({ + type: UPDATE_USER, + payload: { + ROLE: 'VIEWER', + email: '', + name: '', + orgId: '', + orgName: '', + profilePictureURL: '', + userId: '', + }, + }); + + store.dispatch({ + type: UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN, + payload: { + accessJwt: '', + refreshJwt: '', + }, + }); + + history.push(ROUTES.LOGIN); +}; diff --git a/frontend/src/assets/SomethingWentWrong.tsx b/frontend/src/assets/SomethingWentWrong.tsx new file mode 100644 index 0000000000..874515d96a --- /dev/null +++ b/frontend/src/assets/SomethingWentWrong.tsx @@ -0,0 +1,470 @@ +import React from 'react'; + +function SomethingWentWrong(): JSX.Element { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} + +export default SomethingWentWrong; diff --git a/frontend/src/assets/UnAuthorized.tsx b/frontend/src/assets/UnAuthorized.tsx new file mode 100644 index 0000000000..53a9977400 --- /dev/null +++ b/frontend/src/assets/UnAuthorized.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +function UnAuthorized(): JSX.Element { + return ( + + + + + + ); +} + +export default UnAuthorized; diff --git a/frontend/src/components/Editor/index.tsx b/frontend/src/components/Editor/index.tsx index 51a024f607..0ed486e248 100644 --- a/frontend/src/components/Editor/index.tsx +++ b/frontend/src/components/Editor/index.tsx @@ -1,18 +1,22 @@ import MEditor from '@monaco-editor/react'; import React from 'react'; -function Editor({ value }: EditorProps): JSX.Element { +function Editor({ + value, + language = 'yaml', + onChange, + readOnly = false, +}: EditorProps): JSX.Element { return ( { - if (value.current && newValue) { - // eslint-disable-next-line no-param-reassign - value.current = newValue; + if (newValue) { + onChange(newValue); } }} /> @@ -20,7 +24,15 @@ function Editor({ value }: EditorProps): JSX.Element { } interface EditorProps { - value: React.MutableRefObject; + value: string; + language?: string; + onChange: (value: string) => void; + readOnly?: boolean; } +Editor.defaultProps = { + language: undefined, + readOnly: false, +}; + export default Editor; diff --git a/frontend/src/components/Graph/hasData.ts b/frontend/src/components/Graph/hasData.ts index acaa4fac1e..5ba968bc34 100644 --- a/frontend/src/components/Graph/hasData.ts +++ b/frontend/src/components/Graph/hasData.ts @@ -6,7 +6,7 @@ export const hasData = (data: ChartData): boolean => { let hasData = false; try { for (const dataset of datasets) { - if (dataset.data.length > 0 && dataset.data.some((item) => item !== 0)) { + if (dataset.data.length > 0) { hasData = true; break; } diff --git a/frontend/src/components/Graph/index.tsx b/frontend/src/components/Graph/index.tsx index e51ff21616..2194387dd4 100644 --- a/frontend/src/components/Graph/index.tsx +++ b/frontend/src/components/Graph/index.tsx @@ -32,7 +32,7 @@ import { legend } from './Plugin'; import { emptyGraph } from './Plugin/EmptyGraph'; import { LegendsContainer } from './styles'; import { useXAxisTimeUnit } from './xAxisConfig'; -import { getYAxisFormattedValue } from './yAxisConfig'; +import { getToolTipValue, getYAxisFormattedValue } from './yAxisConfig'; Chart.register( LineElement, @@ -115,7 +115,7 @@ function Graph({ label += ': '; } if (context.parsed.y !== null) { - label += getYAxisFormattedValue(context.parsed.y, yAxisUnit); + label += getToolTipValue(context.parsed.y.toString(), yAxisUnit); } return label; }, @@ -160,10 +160,7 @@ function Graph({ ticks: { // Include a dollar sign in the ticks callback(value) { - return getYAxisFormattedValue( - parseInt(value.toString(), 10), - yAxisUnit, - ); + return getYAxisFormattedValue(value.toString(), yAxisUnit); }, }, }, diff --git a/frontend/src/components/Graph/yAxisConfig.ts b/frontend/src/components/Graph/yAxisConfig.ts index 5434eeceb3..5d1eeb5da7 100644 --- a/frontend/src/components/Graph/yAxisConfig.ts +++ b/frontend/src/components/Graph/yAxisConfig.ts @@ -1,12 +1,55 @@ import { formattedValueToString, getValueFormat } from '@grafana/data'; export const getYAxisFormattedValue = ( - value: number, + value: string, format: string, ): string => { + let decimalPrecision: number | undefined; + const parsedValue = getValueFormat(format)( + parseFloat(value), + undefined, + undefined, + undefined, + ); + try { + const decimalSplitted = parsedValue.text.split('.'); + if (decimalSplitted.length === 1) { + decimalPrecision = 0; + } else { + const decimalDigits = decimalSplitted[1].split(''); + decimalPrecision = decimalDigits.length; + let nonZeroCtr = 0; + for (let idx = 0; idx < decimalDigits.length; idx += 1) { + if (decimalDigits[idx] !== '0') { + nonZeroCtr += 1; + if (nonZeroCtr >= 2) { + decimalPrecision = idx + 1; + } + } else if (nonZeroCtr) { + decimalPrecision = idx; + break; + } + } + } + + return formattedValueToString( + getValueFormat(format)( + parseFloat(value), + decimalPrecision, + undefined, + undefined, + ), + ); + } catch (error) { + console.error(error); + } + return `${parseFloat(value)}`; +}; + +export const getToolTipValue = (value: string, format: string): string => { try { return formattedValueToString( - getValueFormat(format)(value, undefined, undefined, undefined), + getValueFormat(format)(parseFloat(value), undefined, undefined, undefined), ); } catch (error) { console.error(error); diff --git a/frontend/src/components/NotFound/index.tsx b/frontend/src/components/NotFound/index.tsx index 85be7276b8..ffe7f30cdc 100644 --- a/frontend/src/components/NotFound/index.tsx +++ b/frontend/src/components/NotFound/index.tsx @@ -1,10 +1,19 @@ +import getLocalStorageKey from 'api/browser/localstorage/get'; import NotFoundImage from 'assets/NotFound'; +import { LOCALSTORAGE } from 'constants/localStorage'; import ROUTES from 'constants/routes'; import React from 'react'; +import { useDispatch } from 'react-redux'; +import { Dispatch } from 'redux'; +import AppActions from 'types/actions'; +import { LOGGED_IN } from 'types/actions/app'; import { Button, Container, Text, TextContainer } from './styles'; function NotFound(): JSX.Element { + const dispatch = useDispatch>(); + const isLoggedIn = getLocalStorageKey(LOCALSTORAGE.IS_LOGGED_IN); + return ( @@ -14,7 +23,20 @@ function NotFound(): JSX.Element { Page Not Found - + { + if (isLoggedIn) { + dispatch({ + type: LOGGED_IN, + payload: { + isLoggedIn: true, + }, + }); + } + }} + to={ROUTES.APPLICATION} + tabIndex={0} + > Return To Metrics Page diff --git a/frontend/src/components/WelcomeLeftContainer/index.tsx b/frontend/src/components/WelcomeLeftContainer/index.tsx new file mode 100644 index 0000000000..ef69a4599d --- /dev/null +++ b/frontend/src/components/WelcomeLeftContainer/index.tsx @@ -0,0 +1,40 @@ +import { Card, Space, Typography } from 'antd'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { Container, LeftContainer, Logo } from './styles'; + +const { Title } = Typography; + +function WelcomeLeftContainer({ + version, + children, +}: WelcomeLeftContainerProps): JSX.Element { + const { t } = useTranslation(); + + return ( + + + + + SigNoz + + {t('monitor_signup')} + + SigNoz {version} + + + {children} + + ); +} + +interface WelcomeLeftContainerProps { + version: string; + children: React.ReactChild; +} + +export default WelcomeLeftContainer; diff --git a/frontend/src/components/WelcomeLeftContainer/styles.ts b/frontend/src/components/WelcomeLeftContainer/styles.ts new file mode 100644 index 0000000000..70428a7f1d --- /dev/null +++ b/frontend/src/components/WelcomeLeftContainer/styles.ts @@ -0,0 +1,22 @@ +import { Space } from 'antd'; +import styled from 'styled-components'; + +export const LeftContainer = styled(Space)` + flex: 1; +`; + +export const Logo = styled.img` + width: 60px; +`; + +export const Container = styled.div` + &&& { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + + max-width: 1024px; + margin: 0 auto; + } +`; diff --git a/frontend/src/constants/app.ts b/frontend/src/constants/app.ts index 90d6222689..35ae663592 100644 --- a/frontend/src/constants/app.ts +++ b/frontend/src/constants/app.ts @@ -7,3 +7,4 @@ export const AUTH0_REDIRECT_PATH = '/redirect'; export const DEFAULT_AUTH0_APP_REDIRECTION_PATH = ROUTES.APPLICATION; export const IS_SIDEBAR_COLLAPSED = 'isSideBarCollapsed'; +export const INVITE_MEMBERS_HASH = '#invite-team-members'; diff --git a/frontend/src/constants/auth.ts b/frontend/src/constants/auth.ts deleted file mode 100644 index ff93594646..0000000000 --- a/frontend/src/constants/auth.ts +++ /dev/null @@ -1 +0,0 @@ -export const IS_LOGGED_IN = 'isLoggedIn'; diff --git a/frontend/src/constants/localStorage.ts b/frontend/src/constants/localStorage.ts index 86d879122d..d45dbc6cec 100644 --- a/frontend/src/constants/localStorage.ts +++ b/frontend/src/constants/localStorage.ts @@ -1,3 +1,6 @@ export enum LOCALSTORAGE { METRICS_TIME_IN_DURATION = 'metricsTimeDurations', + IS_LOGGED_IN = 'IS_LOGGED_IN', + AUTH_TOKEN = 'AUTH_TOKEN', + REFRESH_AUTH_TOKEN = 'REFRESH_AUTH_TOKEN', } diff --git a/frontend/src/constants/resourceAttributes.ts b/frontend/src/constants/resourceAttributes.ts new file mode 100644 index 0000000000..4e82cef590 --- /dev/null +++ b/frontend/src/constants/resourceAttributes.ts @@ -0,0 +1,18 @@ +import { OperatorValues } from 'types/reducer/trace'; + +export const OperatorConversions: Array<{ + label: string; + metricValue: string; + traceValue: OperatorValues; +}> = [ + { + label: 'IN', + metricValue: '=~', + traceValue: 'in', + }, + { + label: 'Not IN', + metricValue: '!~', + traceValue: 'not in', + }, +]; diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts index e62cec59f2..3c7db9c995 100644 --- a/frontend/src/constants/routes.ts +++ b/frontend/src/constants/routes.ts @@ -1,5 +1,6 @@ const ROUTES = { SIGN_UP: '/signup', + LOGIN: '/login', SERVICE_METRICS: '/application/:servicename', SERVICE_MAP: '/service-map', TRACE: '/trace', @@ -17,7 +18,16 @@ const ROUTES = { ALL_CHANNELS: '/settings/channels', CHANNELS_NEW: '/setting/channels/new', CHANNELS_EDIT: '/setting/channels/edit/:id', + ALL_ERROR: '/errors', + ERROR_DETAIL: '/error-detail', VERSION: '/status', + MY_SETTINGS: '/my-settings', + ORG_SETTINGS: '/settings/org-settings', + SOMETHING_WENT_WRONG: '/something-went-wrong', + UN_AUTHORIZED: '/un-authorized', + NOT_FOUND: '/not-found', + HOME_PAGE: '/', + PASSWORD_RESET: '/password-reset', }; export default ROUTES; diff --git a/frontend/src/container/AllAlertChannels/AlertChannels.tsx b/frontend/src/container/AllAlertChannels/AlertChannels.tsx index f537e969e1..974530c6e5 100644 --- a/frontend/src/container/AllAlertChannels/AlertChannels.tsx +++ b/frontend/src/container/AllAlertChannels/AlertChannels.tsx @@ -2,16 +2,22 @@ import { Button, notification, Table } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import ROUTES from 'constants/routes'; +import useComponentPermission from 'hooks/useComponentPermission'; import history from 'lib/history'; import React, { useCallback, useState } from 'react'; +import { useSelector } from 'react-redux'; import { generatePath } from 'react-router-dom'; +import { AppState } from 'store/reducers'; import { Channels, PayloadProps } from 'types/api/channels/getAll'; +import AppReducer from 'types/reducer/app'; import Delete from './Delete'; function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element { const [notifications, Element] = notification.useNotification(); const [channels, setChannels] = useState(allChannels); + const { role } = useSelector((state) => state.app); + const [action] = useComponentPermission(['new_alert_action'], role); const onClickEditHandler = useCallback((id: string) => { history.replace( @@ -32,7 +38,10 @@ function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element { dataIndex: 'type', key: 'type', }, - { + ]; + + if (action) { + columns.push({ title: 'Action', dataIndex: 'id', key: 'action', @@ -45,8 +54,8 @@ function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element { > ), - }, - ]; + }); + } return ( <> diff --git a/frontend/src/container/AllAlertChannels/index.tsx b/frontend/src/container/AllAlertChannels/index.tsx index 4aeaba3354..44ab948f0b 100644 --- a/frontend/src/container/AllAlertChannels/index.tsx +++ b/frontend/src/container/AllAlertChannels/index.tsx @@ -4,16 +4,25 @@ import getAll from 'api/channels/getAll'; import Spinner from 'components/Spinner'; import TextToolTip from 'components/TextToolTip'; import ROUTES from 'constants/routes'; +import useComponentPermission from 'hooks/useComponentPermission'; import useFetch from 'hooks/useFetch'; import history from 'lib/history'; import React, { useCallback } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import AppReducer from 'types/reducer/app'; -import AlertChannlesComponent from './AlertChannels'; +import AlertChannelsComponent from './AlertChannels'; import { Button, ButtonContainer } from './styles'; const { Paragraph } = Typography; function AlertChannels(): JSX.Element { + const { role } = useSelector((state) => state.app); + const [addNewChannelPermission] = useComponentPermission( + ['add_new_channel'], + role, + ); const onToggleHandler = useCallback(() => { history.push(ROUTES.CHANNELS_NEW); }, []); @@ -41,13 +50,15 @@ function AlertChannels(): JSX.Element { url="https://signoz.io/docs/userguide/alerts-management/#setting-notification-channel" /> - }> - New Alert Channel - + {addNewChannelPermission && ( + }> + New Alert Channel + + )} - + > ); } diff --git a/frontend/src/container/AllError/index.tsx b/frontend/src/container/AllError/index.tsx new file mode 100644 index 0000000000..3f49fdb5a4 --- /dev/null +++ b/frontend/src/container/AllError/index.tsx @@ -0,0 +1,114 @@ +import { notification, Table, Typography } from 'antd'; +import { ColumnsType } from 'antd/lib/table'; +import getAll from 'api/errors/getAll'; +import ROUTES from 'constants/routes'; +import dayjs from 'dayjs'; +import React, { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useQuery } from 'react-query'; +import { useSelector } from 'react-redux'; +import { Link } from 'react-router-dom'; +import { AppState } from 'store/reducers'; +import { Exception } from 'types/api/errors/getAll'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +function AllErrors(): JSX.Element { + const { maxTime, minTime } = useSelector( + (state) => state.globalTime, + ); + + const { t } = useTranslation(['common']); + + const { isLoading, data } = useQuery(['getAllError', [maxTime, minTime]], { + queryFn: () => + getAll({ + end: maxTime, + start: minTime, + }), + }); + + useEffect(() => { + if (data?.error) { + notification.error({ + message: data.error || t('something_went_wrong'), + }); + } + }, [data?.error, data?.payload, t]); + + const getDateValue = (value: string): JSX.Element => { + return ( + {dayjs(value).format('DD/MM/YYYY HH:mm:ss A')} + ); + }; + + const columns: ColumnsType = [ + { + title: 'Exception Type', + dataIndex: 'exceptionType', + key: 'exceptionType', + render: (value, record): JSX.Element => ( + + {value} + + ), + sorter: (a, b): number => + a.exceptionType.charCodeAt(0) - b.exceptionType.charCodeAt(0), + }, + { + title: 'Error Message', + dataIndex: 'exceptionMessage', + key: 'exceptionMessage', + render: (value): JSX.Element => ( + + {value} + + ), + }, + { + title: 'Count', + dataIndex: 'exceptionCount', + key: 'exceptionCount', + sorter: (a, b): number => a.exceptionCount - b.exceptionCount, + }, + { + title: 'Last Seen', + dataIndex: 'lastSeen', + key: 'lastSeen', + render: getDateValue, + sorter: (a, b): number => + dayjs(b.lastSeen).isBefore(dayjs(a.lastSeen)) === true ? 1 : 0, + }, + { + title: 'First Seen', + dataIndex: 'firstSeen', + key: 'firstSeen', + render: getDateValue, + sorter: (a, b): number => + dayjs(b.firstSeen).isBefore(dayjs(a.firstSeen)) === true ? 1 : 0, + }, + { + title: 'Application', + dataIndex: 'serviceName', + key: 'serviceName', + sorter: (a, b): number => + a.serviceName.charCodeAt(0) - b.serviceName.charCodeAt(0), + }, + ]; + + return ( + + ); +} + +export default AllErrors; diff --git a/frontend/src/container/AppLayout/index.tsx b/frontend/src/container/AppLayout/index.tsx index 1fa47522c5..5abb5f4d6f 100644 --- a/frontend/src/container/AppLayout/index.tsx +++ b/frontend/src/container/AppLayout/index.tsx @@ -1,13 +1,12 @@ import { notification } from 'antd'; -import getLatestVersion from 'api/user/getLatestVersion'; -import getVersion from 'api/user/getVersion'; -import ROUTES from 'constants/routes'; -import TopNav from 'container/Header'; +import getUserLatestVersion from 'api/user/getLatestVersion'; +import getUserVersion from 'api/user/getVersion'; +import Header from 'container/Header'; import SideNav from 'container/SideNav'; -import useFetch from 'hooks/useFetch'; -import history from 'lib/history'; -import React, { ReactNode, useEffect, useRef, useState } from 'react'; +import TopNav from 'container/TopNav'; +import React, { ReactNode, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; +import { useQueries } from 'react-query'; import { useDispatch, useSelector } from 'react-redux'; import { useLocation } from 'react-router-dom'; import { Dispatch } from 'redux'; @@ -21,47 +20,49 @@ import { } from 'types/actions/app'; import AppReducer from 'types/reducer/app'; -import { Content, Layout } from './styles'; +import { ChildrenContainer, Layout } from './styles'; function AppLayout(props: AppLayoutProps): JSX.Element { const { isLoggedIn } = useSelector((state) => state.app); const { pathname } = useLocation(); const { t } = useTranslation(); - const [isSignUpPage, setIsSignUpPage] = useState(ROUTES.SIGN_UP === pathname); + const [getUserVersionResponse, getUserLatestVersionResponse] = useQueries([ + { + queryFn: getUserVersion, + queryKey: 'getUserVersion', + enabled: isLoggedIn, + }, + { + queryFn: getUserLatestVersion, + queryKey: 'getUserLatestVersion', + enabled: isLoggedIn, + }, + ]); - const { payload: versionPayload, loading, error: getVersionError } = useFetch( - getVersion, - ); + useEffect(() => { + if (getUserLatestVersionResponse.status === 'idle' && isLoggedIn) { + getUserLatestVersionResponse.refetch(); + } - const { - payload: latestVersionPayload, - loading: latestLoading, - error: latestError, - } = useFetch(getLatestVersion); + if (getUserVersionResponse.status === 'idle' && isLoggedIn) { + getUserVersionResponse.refetch(); + } + }, [getUserLatestVersionResponse, getUserVersionResponse, isLoggedIn]); const { children } = props; const dispatch = useDispatch>(); - useEffect(() => { - if (!isLoggedIn) { - setIsSignUpPage(true); - history.push(ROUTES.SIGN_UP); - } else if (isSignUpPage) { - setIsSignUpPage(false); - } - }, [isLoggedIn, isSignUpPage]); - const latestCurrentCounter = useRef(0); const latestVersionCounter = useRef(0); useEffect(() => { - if (isLoggedIn && pathname === ROUTES.SIGN_UP) { - history.push(ROUTES.APPLICATION); - } - - if (!latestLoading && latestError && latestCurrentCounter.current === 0) { + if ( + getUserLatestVersionResponse.isFetched && + getUserLatestVersionResponse.isError && + latestCurrentCounter.current === 0 + ) { latestCurrentCounter.current = 1; dispatch({ @@ -75,7 +76,11 @@ function AppLayout(props: AppLayoutProps): JSX.Element { }); } - if (!loading && getVersionError && latestVersionCounter.current === 0) { + if ( + getUserVersionResponse.isFetched && + getUserVersionResponse.isError && + latestVersionCounter.current === 0 + ) { latestVersionCounter.current = 1; dispatch({ @@ -89,44 +94,62 @@ function AppLayout(props: AppLayoutProps): JSX.Element { }); } - if (!latestLoading && versionPayload) { + if ( + getUserVersionResponse.isFetched && + getUserLatestVersionResponse.isSuccess && + getUserVersionResponse.data && + getUserVersionResponse.data.payload + ) { dispatch({ type: UPDATE_CURRENT_VERSION, payload: { - currentVersion: versionPayload.version, + currentVersion: getUserVersionResponse.data.payload.version, }, }); } - if (!loading && latestVersionPayload) { + if ( + getUserLatestVersionResponse.isFetched && + getUserLatestVersionResponse.isSuccess && + getUserLatestVersionResponse.data && + getUserLatestVersionResponse.data.payload + ) { dispatch({ type: UPDATE_LATEST_VERSION, payload: { - latestVersion: latestVersionPayload.name, + latestVersion: getUserLatestVersionResponse.data.payload.name, }, }); } }, [ dispatch, - loading, - latestLoading, - versionPayload, - latestVersionPayload, isLoggedIn, pathname, - getVersionError, - latestError, t, + getUserLatestVersionResponse.isLoading, + getUserLatestVersionResponse.isError, + getUserLatestVersionResponse.data, + getUserVersionResponse.isLoading, + getUserVersionResponse.isError, + getUserVersionResponse.data, + getUserLatestVersionResponse.isFetched, + getUserVersionResponse.isFetched, + getUserLatestVersionResponse.isSuccess, ]); + const isToDisplayLayout = isLoggedIn; + return ( - {!isSignUpPage && } + {isToDisplayLayout && } - - {!isSignUpPage && } - {children} - + {isToDisplayLayout && } + + + {isToDisplayLayout && } + {children} + + ); diff --git a/frontend/src/container/AppLayout/styles.ts b/frontend/src/container/AppLayout/styles.ts index f3e9d573b0..71547d1592 100644 --- a/frontend/src/container/AppLayout/styles.ts +++ b/frontend/src/container/AppLayout/styles.ts @@ -3,16 +3,15 @@ import styled from 'styled-components'; export const Layout = styled(LayoutComponent)` &&& { - min-height: 100vh; + min-height: 91vh; display: flex; position: relative; } `; -export const Content = styled(LayoutComponent.Content)` - &&& { - margin: 0 1rem; - display: flex; - flex-direction: column; - } +export const ChildrenContainer = styled.div` + margin: 0 1rem; + display: flex; + flex-direction: column; + height: 100%; `; diff --git a/frontend/src/container/CreateAlertChannels/config.ts b/frontend/src/container/CreateAlertChannels/config.ts index f104a84076..6c89764637 100644 --- a/frontend/src/container/CreateAlertChannels/config.ts +++ b/frontend/src/container/CreateAlertChannels/config.ts @@ -1,6 +1,7 @@ export interface Channel { send_resolved?: boolean; name: string; + filter?: Partial>; } export interface SlackChannel extends Channel { @@ -17,6 +18,66 @@ export interface WebhookChannel extends Channel { password?: string; } -export type ChannelType = 'slack' | 'email' | 'webhook'; +// PagerChannel configures alert manager to send +// events to pagerduty +export interface PagerChannel extends Channel { + // ref: https://prometheus.io/docs/alerting/latest/configuration/#pagerduty_config + routing_key?: string; + // displays source of the event in pager duty + client?: string; + client_url?: string; + // A description of the incident + description?: string; + // Severity of the incident + severity?: string; + // The part or component of the affected system that is broken + component?: string; + // A cluster or grouping of sources + group?: string; + // The class/type of the event. + class?: string; + + details?: string; + detailsArray?: Record; +} +export const ValidatePagerChannel = (p: PagerChannel): string => { + if (!p) { + return 'Received unexpected input for this channel, please contact your administrator '; + } + + if (!p.name || p.name === '') { + return 'Name is mandatory for creating a channel'; + } + + if (!p.routing_key || p.routing_key === '') { + return 'Routing Key is mandatory for creating pagerduty channel'; + } + + // validate details json + try { + JSON.parse(p.details || '{}'); + } catch (e) { + return 'failed to parse additional information, please enter a valid json'; + } + + return ''; +}; + +export type ChannelType = 'slack' | 'email' | 'webhook' | 'pagerduty'; export const SlackType: ChannelType = 'slack'; export const WebhookType: ChannelType = 'webhook'; +export const PagerType: ChannelType = 'pagerduty'; + +// LabelFilterStatement will be used for preparing filter conditions / matchers +export interface LabelFilterStatement { + // ref: https://prometheus.io/docs/alerting/latest/configuration/#matcher + + // label name + name: string; + + // comparators supported by promql are =, !=, =~, or !~. = + comparator: string; + + // filter value + value: string; +} diff --git a/frontend/src/container/CreateAlertChannels/defaults.ts b/frontend/src/container/CreateAlertChannels/defaults.ts new file mode 100644 index 0000000000..ac15056703 --- /dev/null +++ b/frontend/src/container/CreateAlertChannels/defaults.ts @@ -0,0 +1,22 @@ +import { PagerChannel } from './config'; + +export const PagerInitialConfig: Partial = { + description: `{{ range .Alerts -}} + *Alert:* {{ if .Annotations.title }} {{ .Annotations.title }} {{ else }} {{ .Annotations.summary }} {{end}} {{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }} + + *Description:* {{ .Annotations.description }} + + *Details:* + {{ range .Labels.SortedPairs }} • *{{ .Name }}:* {{ .Value }} + {{ end }} + {{ end }}`, + severity: '{{ (index .Alerts 0).Labels.severity }}', + client: 'SigNoz Alert Manager', + client_url: 'https://enter-signoz-host-n-port-here/alerts', + details: JSON.stringify({ + firing: `{{ template "pagerduty.default.instances" .Alerts.Firing }}`, + resolved: `{{ template "pagerduty.default.instances" .Alerts.Resolved }}`, + num_firing: '{{ .Alerts.Firing | len }}', + num_resolved: '{{ .Alerts.Resolved | len }}', + }), +}; diff --git a/frontend/src/container/CreateAlertChannels/index.tsx b/frontend/src/container/CreateAlertChannels/index.tsx index 02cd7b274a..fb5ea8a3f6 100644 --- a/frontend/src/container/CreateAlertChannels/index.tsx +++ b/frontend/src/container/CreateAlertChannels/index.tsx @@ -1,26 +1,38 @@ import { Form, notification } from 'antd'; +import createPagerApi from 'api/channels/createPager'; import createSlackApi from 'api/channels/createSlack'; import createWebhookApi from 'api/channels/createWebhook'; +import testPagerApi from 'api/channels/testPager'; +import testSlackApi from 'api/channels/testSlack'; +import testWebhookApi from 'api/channels/testWebhook'; import ROUTES from 'constants/routes'; import FormAlertChannels from 'container/FormAlertChannels'; import history from 'lib/history'; import React, { useCallback, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { ChannelType, + PagerChannel, + PagerType, SlackChannel, SlackType, + ValidatePagerChannel, WebhookChannel, WebhookType, } from './config'; +import { PagerInitialConfig } from './defaults'; function CreateAlertChannels({ preType = 'slack', }: CreateAlertChannelsProps): JSX.Element { + // init namespace for translations + const { t } = useTranslation('channels'); + const [formInstance] = Form.useForm(); const [selectedConfig, setSelectedConfig] = useState< - Partial + Partial >({ text: `{{ range .Alerts -}} *Alert:* {{ .Labels.alertname }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }} @@ -45,33 +57,48 @@ function CreateAlertChannels({ {{- end }}`, }); const [savingState, setSavingState] = useState(false); + const [testingState, setTestingState] = useState(false); const [notifications, NotificationElement] = notification.useNotification(); const [type, setType] = useState(preType); - const onTypeChangeHandler = useCallback((value: string) => { - setType(value as ChannelType); - }, []); + const onTypeChangeHandler = useCallback( + (value: string) => { + const currentType = type; + setType(value as ChannelType); - const onTestHandler = useCallback(() => { - console.log('test'); - }, []); + if (value === PagerType && currentType !== value) { + // reset config to pager defaults + setSelectedConfig({ + name: selectedConfig?.name, + send_resolved: selectedConfig.send_resolved, + ...PagerInitialConfig, + }); + } + }, + [type, selectedConfig], + ); + + const prepareSlackRequest = useCallback(() => { + return { + api_url: selectedConfig?.api_url || '', + channel: selectedConfig?.channel || '', + name: selectedConfig?.name || '', + send_resolved: true, + text: selectedConfig?.text || '', + title: selectedConfig?.title || '', + }; + }, [selectedConfig]); const onSlackHandler = useCallback(async () => { + setSavingState(true); + try { - setSavingState(true); - const response = await createSlackApi({ - api_url: selectedConfig?.api_url || '', - channel: selectedConfig?.channel || '', - name: selectedConfig?.name || '', - send_resolved: true, - text: selectedConfig?.text || '', - title: selectedConfig?.title || '', - }); + const response = await createSlackApi(prepareSlackRequest()); if (response.statusCode === 200) { notifications.success({ message: 'Success', - description: 'Successfully created the channel', + description: t('channel_creation_done'), }); setTimeout(() => { history.replace(ROUTES.SETTINGS); @@ -79,21 +106,19 @@ function CreateAlertChannels({ } else { notifications.error({ message: 'Error', - description: response.error || 'Error while creating the channel', + description: response.error || t('channel_creation_failed'), }); } - setSavingState(false); } catch (error) { notifications.error({ message: 'Error', - description: - 'An unexpected error occurred while creating this channel, please try again', + description: t('channel_creation_failed'), }); - setSavingState(false); } - }, [notifications, selectedConfig]); + setSavingState(false); + }, [prepareSlackRequest, t, notifications]); - const onWebhookHandler = useCallback(async () => { + const prepareWebhookRequest = useCallback(() => { // initial api request without auth params let request: WebhookChannel = { api_url: selectedConfig?.api_url || '', @@ -101,39 +126,42 @@ function CreateAlertChannels({ send_resolved: true, }; - setSavingState(true); - - try { - if (selectedConfig?.username !== '' || selectedConfig?.password !== '') { - if (selectedConfig?.username !== '') { - // if username is not null then password must be passed - if (selectedConfig?.password !== '') { - request = { - ...request, - username: selectedConfig.username, - password: selectedConfig.password, - }; - } else { - notifications.error({ - message: 'Error', - description: 'A Password must be provided with user name', - }); - } - } else if (selectedConfig?.password !== '') { - // only password entered, set bearer token + if (selectedConfig?.username !== '' || selectedConfig?.password !== '') { + if (selectedConfig?.username !== '') { + // if username is not null then password must be passed + if (selectedConfig?.password !== '') { request = { ...request, - username: '', + username: selectedConfig.username, password: selectedConfig.password, }; + } else { + notifications.error({ + message: 'Error', + description: t('username_no_password'), + }); } + } else if (selectedConfig?.password !== '') { + // only password entered, set bearer token + request = { + ...request, + username: '', + password: selectedConfig.password, + }; } + } + return request; + }, [notifications, t, selectedConfig]); + const onWebhookHandler = useCallback(async () => { + setSavingState(true); + try { + const request = prepareWebhookRequest(); const response = await createWebhookApi(request); if (response.statusCode === 200) { notifications.success({ message: 'Success', - description: 'Successfully created the channel', + description: t('channel_creation_done'), }); setTimeout(() => { history.replace(ROUTES.SETTINGS); @@ -141,18 +169,75 @@ function CreateAlertChannels({ } else { notifications.error({ message: 'Error', - description: response.error || 'Error while creating the channel', + description: response.error || t('channel_creation_failed'), }); } } catch (error) { notifications.error({ message: 'Error', - description: - 'An unexpected error occurred while creating this channel, please try again', + description: t('channel_creation_failed'), }); } setSavingState(false); - }, [notifications, selectedConfig]); + }, [prepareWebhookRequest, t, notifications]); + + const preparePagerRequest = useCallback(() => { + const validationError = ValidatePagerChannel(selectedConfig as PagerChannel); + if (validationError !== '') { + notifications.error({ + message: 'Error', + description: validationError, + }); + return null; + } + + return { + name: selectedConfig?.name || '', + send_resolved: true, + routing_key: selectedConfig?.routing_key || '', + client: selectedConfig?.client || '', + client_url: selectedConfig?.client_url || '', + description: selectedConfig?.description || '', + severity: selectedConfig?.severity || '', + component: selectedConfig?.component || '', + group: selectedConfig?.group || '', + class: selectedConfig?.class || '', + details: selectedConfig.details || '', + detailsArray: JSON.parse(selectedConfig.details || '{}'), + }; + }, [selectedConfig, notifications]); + + const onPagerHandler = useCallback(async () => { + setSavingState(true); + const request = preparePagerRequest(); + + if (request) { + try { + const response = await createPagerApi(request); + + if (response.statusCode === 200) { + notifications.success({ + message: 'Success', + description: t('channel_creation_done'), + }); + setTimeout(() => { + history.replace(ROUTES.SETTINGS); + }, 2000); + } else { + notifications.error({ + message: 'Error', + description: response.error || t('channel_creation_failed'), + }); + } + } catch (e) { + notifications.error({ + message: 'Error', + description: t('channel_creation_failed'), + }); + } + } + setSavingState(false); + }, [t, notifications, preparePagerRequest]); const onSaveHandler = useCallback( async (value: ChannelType) => { @@ -163,14 +248,80 @@ function CreateAlertChannels({ case WebhookType: onWebhookHandler(); break; + case PagerType: + onPagerHandler(); + break; default: notifications.error({ message: 'Error', - description: 'channel type selected is invalid', + description: t('selected_channel_invalid'), }); } }, - [onSlackHandler, onWebhookHandler, notifications], + [onSlackHandler, t, onPagerHandler, onWebhookHandler, notifications], + ); + + const performChannelTest = useCallback( + async (channelType: ChannelType) => { + setTestingState(true); + try { + let request; + let response; + switch (channelType) { + case WebhookType: + request = prepareWebhookRequest(); + response = await testWebhookApi(request); + break; + case SlackType: + request = prepareSlackRequest(); + response = await testSlackApi(request); + break; + case PagerType: + request = preparePagerRequest(); + if (request) response = await testPagerApi(request); + break; + default: + notifications.error({ + message: 'Error', + description: t('test_unsupported'), + }); + setTestingState(false); + return; + } + + if (response && response.statusCode === 200) { + notifications.success({ + message: 'Success', + description: t('channel_test_done'), + }); + } else { + notifications.error({ + message: 'Error', + description: t('channel_test_failed'), + }); + } + } catch (error) { + notifications.error({ + message: 'Error', + description: t('channel_test_unexpected'), + }); + } + setTestingState(false); + }, + [ + prepareWebhookRequest, + t, + preparePagerRequest, + prepareSlackRequest, + notifications, + ], + ); + + const onTestHandler = useCallback( + async (value: ChannelType) => { + performChannelTest(value); + }, + [performChannelTest], ); return ( @@ -183,11 +334,13 @@ function CreateAlertChannels({ onTestHandler, onSaveHandler, savingState, + testingState, NotificationElement, - title: 'New Notification Channels', + title: t('page_title_create'), initialValue: { type, ...selectedConfig, + ...PagerInitialConfig, }, }} /> diff --git a/frontend/src/container/EditAlertChannels/index.tsx b/frontend/src/container/EditAlertChannels/index.tsx index e4aab19d31..ef8f6a0a2e 100644 --- a/frontend/src/container/EditAlertChannels/index.tsx +++ b/frontend/src/container/EditAlertChannels/index.tsx @@ -1,29 +1,41 @@ import { Form, notification } from 'antd'; +import editPagerApi from 'api/channels/editPager'; import editSlackApi from 'api/channels/editSlack'; import editWebhookApi from 'api/channels/editWebhook'; +import testPagerApi from 'api/channels/testPager'; +import testSlackApi from 'api/channels/testSlack'; +import testWebhookApi from 'api/channels/testWebhook'; import ROUTES from 'constants/routes'; import { ChannelType, + PagerChannel, + PagerType, SlackChannel, SlackType, + ValidatePagerChannel, WebhookChannel, WebhookType, } from 'container/CreateAlertChannels/config'; import FormAlertChannels from 'container/FormAlertChannels'; import history from 'lib/history'; import React, { useCallback, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; function EditAlertChannels({ initialValue, }: EditAlertChannelsProps): JSX.Element { + // init namespace for translations + const { t } = useTranslation('channels'); + const [formInstance] = Form.useForm(); const [selectedConfig, setSelectedConfig] = useState< - Partial + Partial >({ ...initialValue, }); const [savingState, setSavingState] = useState(false); + const [testingState, setTestingState] = useState(false); const [notifications, NotificationElement] = notification.useNotification(); const { id } = useParams<{ id: string }>(); @@ -35,9 +47,8 @@ function EditAlertChannels({ setType(value as ChannelType); }, []); - const onSlackEditHandler = useCallback(async () => { - setSavingState(true); - const response = await editSlackApi({ + const prepareSlackRequest = useCallback(() => { + return { api_url: selectedConfig?.api_url || '', channel: selectedConfig?.channel || '', name: selectedConfig?.name || '', @@ -45,12 +56,27 @@ function EditAlertChannels({ text: selectedConfig?.text || '', title: selectedConfig?.title || '', id, - }); + }; + }, [id, selectedConfig]); + + const onSlackEditHandler = useCallback(async () => { + setSavingState(true); + + if (selectedConfig?.api_url === '') { + notifications.error({ + message: 'Error', + description: t('webhook_url_required'), + }); + setSavingState(false); + return; + } + + const response = await editSlackApi(prepareSlackRequest()); if (response.statusCode === 200) { notifications.success({ message: 'Success', - description: 'Channels Edited Successfully', + description: t('channel_edit_done'), }); setTimeout(() => { @@ -59,15 +85,27 @@ function EditAlertChannels({ } else { notifications.error({ message: 'Error', - description: response.error || 'error while updating the Channels', + description: response.error || t('channel_edit_failed'), }); } setSavingState(false); - }, [selectedConfig, notifications, id]); + }, [prepareSlackRequest, t, notifications, selectedConfig]); + + const prepareWebhookRequest = useCallback(() => { + const { name, username, password } = selectedConfig; + return { + api_url: selectedConfig?.api_url || '', + name: name || '', + send_resolved: true, + username, + password, + id, + }; + }, [id, selectedConfig]); const onWebhookEditHandler = useCallback(async () => { setSavingState(true); - const { name, username, password } = selectedConfig; + const { username, password } = selectedConfig; const showError = (msg: string): void => { notifications.error({ @@ -77,40 +115,82 @@ function EditAlertChannels({ }; if (selectedConfig?.api_url === '') { - showError('Webhook URL is mandatory'); + showError(t('webhook_url_required')); setSavingState(false); return; } if (username && (!password || password === '')) { - showError('Please enter a password'); + showError(t('username_no_password')); setSavingState(false); return; } - const response = await editWebhookApi({ - api_url: selectedConfig?.api_url || '', - name: name || '', - send_resolved: true, - username, - password, - id, - }); + const response = await editWebhookApi(prepareWebhookRequest()); if (response.statusCode === 200) { notifications.success({ message: 'Success', - description: 'Channels Edited Successfully', + description: t('channel_edit_done'), }); setTimeout(() => { history.replace(ROUTES.SETTINGS); }, 2000); } else { - showError(response.error || 'error while updating the Channels'); + showError(response.error || t('channel_edit_failed')); } setSavingState(false); - }, [selectedConfig, notifications, id]); + }, [prepareWebhookRequest, t, notifications, selectedConfig]); + + const preparePagerRequest = useCallback(() => { + return { + name: selectedConfig.name || '', + routing_key: selectedConfig.routing_key, + client: selectedConfig.client, + client_url: selectedConfig.client_url, + description: selectedConfig.description, + severity: selectedConfig.severity, + component: selectedConfig.component, + class: selectedConfig.class, + group: selectedConfig.group, + details: selectedConfig.details, + detailsArray: JSON.parse(selectedConfig.details || '{}'), + id, + }; + }, [id, selectedConfig]); + + const onPagerEditHandler = useCallback(async () => { + setSavingState(true); + const validationError = ValidatePagerChannel(selectedConfig as PagerChannel); + + if (validationError !== '') { + notifications.error({ + message: 'Error', + description: validationError, + }); + setSavingState(false); + return; + } + const response = await editPagerApi(preparePagerRequest()); + + if (response.statusCode === 200) { + notifications.success({ + message: 'Success', + description: t('channel_edit_done'), + }); + + setTimeout(() => { + history.replace(ROUTES.SETTINGS); + }, 2000); + } else { + notifications.error({ + message: 'Error', + description: response.error || t('channel_edit_failed'), + }); + } + setSavingState(false); + }, [preparePagerRequest, notifications, selectedConfig, t]); const onSaveHandler = useCallback( (value: ChannelType) => { @@ -118,14 +198,75 @@ function EditAlertChannels({ onSlackEditHandler(); } else if (value === WebhookType) { onWebhookEditHandler(); + } else if (value === PagerType) { + onPagerEditHandler(); } }, - [onSlackEditHandler, onWebhookEditHandler], + [onSlackEditHandler, onWebhookEditHandler, onPagerEditHandler], ); - const onTestHandler = useCallback(() => { - console.log('test'); - }, []); + const performChannelTest = useCallback( + async (channelType: ChannelType) => { + setTestingState(true); + try { + let request; + let response; + switch (channelType) { + case WebhookType: + request = prepareWebhookRequest(); + response = await testWebhookApi(request); + break; + case SlackType: + request = prepareSlackRequest(); + response = await testSlackApi(request); + break; + case PagerType: + request = preparePagerRequest(); + if (request) response = await testPagerApi(request); + break; + default: + notifications.error({ + message: 'Error', + description: t('test_unsupported'), + }); + setTestingState(false); + return; + } + + if (response && response.statusCode === 200) { + notifications.success({ + message: 'Success', + description: t('channel_test_done'), + }); + } else { + notifications.error({ + message: 'Error', + description: t('channel_test_failed'), + }); + } + } catch (error) { + notifications.error({ + message: 'Error', + description: t('channel_test_failed'), + }); + } + setTestingState(false); + }, + [ + t, + prepareWebhookRequest, + preparePagerRequest, + prepareSlackRequest, + notifications, + ], + ); + + const onTestHandler = useCallback( + async (value: ChannelType) => { + performChannelTest(value); + }, + [performChannelTest], + ); return ( ); diff --git a/frontend/src/container/EditRules/index.tsx b/frontend/src/container/EditRules/index.tsx index 342fd5c494..e228af0a10 100644 --- a/frontend/src/container/EditRules/index.tsx +++ b/frontend/src/container/EditRules/index.tsx @@ -5,14 +5,14 @@ import Editor from 'components/Editor'; import ROUTES from 'constants/routes'; import { State } from 'hooks/useFetch'; import history from 'lib/history'; -import React, { useCallback, useRef, useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { PayloadProps } from 'types/api/alerts/get'; import { PayloadProps as PutPayloadProps } from 'types/api/alerts/put'; import { ButtonContainer } from './styles'; function EditRules({ initialData, ruleId }: EditRulesProps): JSX.Element { - const value = useRef(initialData); + const [value, setEditorValue] = useState(initialData); const [notifications, Element] = notification.useNotification(); const [editButtonState, setEditButtonState] = useState>( { @@ -31,7 +31,7 @@ function EditRules({ initialData, ruleId }: EditRulesProps): JSX.Element { loading: true, })); const response = await put({ - data: value.current, + data: value, id: parseInt(ruleId, 10), }); @@ -72,13 +72,13 @@ function EditRules({ initialData, ruleId }: EditRulesProps): JSX.Element { 'Oops! Some issue occured in editing the alert please try again or contact support@signoz.io', }); } - }, [ruleId, notifications]); + }, [value, ruleId, notifications]); return ( <> {Element} - + setEditorValue(value)} value={value} /> (false); + const { t } = useTranslation(['errorDetails', 'common']); + + const { search } = useLocation(); + const params = new URLSearchParams(search); + const queryErrorId = params.get('errorId'); + const serviceName = params.get('serviceName'); + const errorType = params.get('errorType'); + + const errorDetail = idPayload; + + const [stackTraceValue] = useState(errorDetail.exceptionStacktrace); + + const columns = useMemo( + () => [ + { + title: 'Key', + dataIndex: 'key', + key: 'key', + }, + { + title: 'Value', + dataIndex: 'value', + key: 'value', + }, + ], + [], + ); + + const keyToExclude = useMemo( + () => [ + 'exceptionStacktrace', + 'exceptionType', + 'errorId', + 'timestamp', + 'exceptionMessage', + 'newerErrorId', + 'olderErrorId', + ], + [], + ); + + const onClickErrorIdHandler = async (id: string): Promise => { + try { + setLoading(true); + + if (id.length === 0) { + notification.error({ + message: 'Error Id cannot be empty', + }); + setLoading(false); + return; + } + + setLoading(false); + + history.push( + `${history.location.pathname}?errorId=${id}&serviceName=${serviceName}&errorType=${errorType}`, + ); + } catch (error) { + notification.error({ + message: t('something_went_wrong'), + }); + setLoading(false); + } + }; + + const timeStamp = dayjs(errorDetail.timestamp); + + const data: { key: string; value: string }[] = Object.keys(errorDetail) + .filter((e) => !keyToExclude.includes(e)) + .map((key) => ({ + key, + value: errorDetail[key as keyof GetByErrorTypeAndServicePayload], + })); + + const onClickTraceHandler = (): void => { + history.push(`/trace/${errorDetail.traceID}?spanId=${errorDetail.spanID}`); + }; + + return ( + <> + {errorDetail.exceptionType} + {errorDetail.exceptionMessage} + + + + + Event {errorDetail.errorId} + {timeStamp.format('MMM DD YYYY hh:mm:ss A')} + + + + => + onClickErrorIdHandler(errorDetail.olderErrorId) + } + > + {t('older')} + + => + onClickErrorIdHandler(errorDetail.newerErrorId) + } + > + {t('newer')} + + + + + + + {t('see_trace_graph')} + + {t('see_error_in_trace_graph')} + + + + {t('stack_trace')} + {}} value={stackTraceValue} readOnly /> + + + + + + + > + ); +} + +interface ErrorDetailsProps { + idPayload: PayloadProps; +} + +export default ErrorDetails; diff --git a/frontend/src/container/ErrorDetails/styles.ts b/frontend/src/container/ErrorDetails/styles.ts new file mode 100644 index 0000000000..d1cd0327a5 --- /dev/null +++ b/frontend/src/container/ErrorDetails/styles.ts @@ -0,0 +1,28 @@ +import { grey } from '@ant-design/colors'; +import styled from 'styled-components'; + +export const DashedContainer = styled.div` + border: ${`1px dashed ${grey[0]}`}; + box-sizing: border-box; + border-radius: 0.25rem; + display: flex; + justify-content: space-between; + padding: 1rem; + margin-top: 1.875rem; + margin-bottom: 1.625rem; + align-items: center; +`; + +export const ButtonContainer = styled.div` + display: flex; + gap: 1rem; +`; + +export const EventContainer = styled.div` + display: flex; + justify-content: space-between; +`; + +export const EditorContainer = styled.div` + margin-top: 1.5rem; +`; diff --git a/frontend/src/container/FormAlertChannels/Settings/LabelFilter.tsx b/frontend/src/container/FormAlertChannels/Settings/LabelFilter.tsx new file mode 100644 index 0000000000..2d71c520ce --- /dev/null +++ b/frontend/src/container/FormAlertChannels/Settings/LabelFilter.tsx @@ -0,0 +1,64 @@ +import { Input, Select } from 'antd'; +import FormItem from 'antd/lib/form/FormItem'; +import { LabelFilterStatement } from 'container/CreateAlertChannels/config'; +import React from 'react'; + +const { Option } = Select; + +// LabelFilterForm supports filters or matchers on alert notifications +// presently un-used but will be introduced to the channel creation at some +// point +function LabelFilterForm({ setFilter }: LabelFilterProps): JSX.Element { + return ( + + + { + setFilter((value) => { + const first: LabelFilterStatement = value[0] as LabelFilterStatement; + first.name = event; + return [first]; + }); + }} + > + Severity + Service + + { + setFilter((value) => { + const first: LabelFilterStatement = value[0] as LabelFilterStatement; + first.comparator = event; + return [first]; + }); + }} + > + = + != + + { + setFilter((value) => { + const first: LabelFilterStatement = value[0] as LabelFilterStatement; + first.value = event.target.value; + return [first]; + }); + }} + /> + + + ); +} + +export interface LabelFilterProps { + setFilter: React.Dispatch< + React.SetStateAction>> + >; +} + +export default LabelFilterForm; diff --git a/frontend/src/container/FormAlertChannels/Settings/Pager.tsx b/frontend/src/container/FormAlertChannels/Settings/Pager.tsx new file mode 100644 index 0000000000..0e36613096 --- /dev/null +++ b/frontend/src/container/FormAlertChannels/Settings/Pager.tsx @@ -0,0 +1,155 @@ +import { Input } from 'antd'; +import FormItem from 'antd/lib/form/FormItem'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { PagerChannel } from '../../CreateAlertChannels/config'; + +const { TextArea } = Input; + +function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element { + const { t } = useTranslation('channels'); + return ( + <> + + { + setSelectedConfig((value) => ({ + ...value, + routing_key: event.target.value, + })); + }} + /> + + + + + setSelectedConfig((value) => ({ + ...value, + description: event.target.value, + })) + } + placeholder={t('placeholder_pager_description')} + /> + + + + + setSelectedConfig((value) => ({ + ...value, + severity: event.target.value, + })) + } + /> + + + + + setSelectedConfig((value) => ({ + ...value, + details: event.target.value, + })) + } + /> + + + + + setSelectedConfig((value) => ({ + ...value, + component: event.target.value, + })) + } + /> + + + + + setSelectedConfig((value) => ({ + ...value, + group: event.target.value, + })) + } + /> + + + + + setSelectedConfig((value) => ({ + ...value, + class: event.target.value, + })) + } + /> + + + + setSelectedConfig((value) => ({ + ...value, + client: event.target.value, + })) + } + /> + + + + + setSelectedConfig((value) => ({ + ...value, + client_url: event.target.value, + })) + } + /> + + > + ); +} + +interface PagerFormProps { + setSelectedConfig: React.Dispatch>>; +} + +export default PagerForm; diff --git a/frontend/src/container/FormAlertChannels/Settings/Slack.tsx b/frontend/src/container/FormAlertChannels/Settings/Slack.tsx index 738546c58e..89ea3d62f9 100644 --- a/frontend/src/container/FormAlertChannels/Settings/Slack.tsx +++ b/frontend/src/container/FormAlertChannels/Settings/Slack.tsx @@ -1,15 +1,18 @@ import { Input } from 'antd'; import FormItem from 'antd/lib/form/FormItem'; import React from 'react'; +import { useTranslation } from 'react-i18next'; import { SlackChannel } from '../../CreateAlertChannels/config'; const { TextArea } = Input; function Slack({ setSelectedConfig }: SlackProps): JSX.Element { + const { t } = useTranslation('channels'); + return ( <> - + { setSelectedConfig((value) => ({ @@ -22,8 +25,8 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element { @@ -35,7 +38,7 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element { /> - + - + setSelectedConfig((value) => ({ @@ -56,7 +59,7 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element { text: event.target.value, })) } - placeholder="description" + placeholder={t('placeholder_slack_description')} /> > diff --git a/frontend/src/container/FormAlertChannels/Settings/Webhook.tsx b/frontend/src/container/FormAlertChannels/Settings/Webhook.tsx index 1c7748f795..d8e8c04aff 100644 --- a/frontend/src/container/FormAlertChannels/Settings/Webhook.tsx +++ b/frontend/src/container/FormAlertChannels/Settings/Webhook.tsx @@ -1,13 +1,16 @@ import { Input } from 'antd'; import FormItem from 'antd/lib/form/FormItem'; import React from 'react'; +import { useTranslation } from 'react-i18next'; import { WebhookChannel } from '../../CreateAlertChannels/config'; function WebhookSettings({ setSelectedConfig }: WebhookProps): JSX.Element { + const { t } = useTranslation('channels'); + return ( <> - + { setSelectedConfig((value) => ({ @@ -19,8 +22,8 @@ function WebhookSettings({ setSelectedConfig }: WebhookProps): JSX.Element { { @@ -34,7 +37,7 @@ function WebhookSettings({ setSelectedConfig }: WebhookProps): JSX.Element { { switch (type) { case SlackType: return ; case WebhookType: return ; + case PagerType: + return ; + default: return null; } @@ -48,9 +59,9 @@ function FormAlertChannels({ {title} - + { setSelectedConfig((state) => ({ ...state, @@ -60,14 +71,17 @@ function FormAlertChannels({ /> - - + + Slack Webhook + + Pagerduty + @@ -80,15 +94,21 @@ function FormAlertChannels({ type="primary" onClick={(): void => onSaveHandler(type)} > - Save + {t('button_save_channel')} + + onTestHandler(type)} + > + {t('button_test_channel')} - {/* Test */} { history.replace(ROUTES.SETTINGS); }} > - Back + {t('button_return')} @@ -99,9 +119,13 @@ function FormAlertChannels({ interface FormAlertChannelsProps { formInstance: FormInstance; type: ChannelType; - setSelectedConfig: React.Dispatch>>; + setSelectedConfig: React.Dispatch< + React.SetStateAction> + >; onTypeChangeHandler: (value: ChannelType) => void; onSaveHandler: (props: ChannelType) => void; + onTestHandler: (props: ChannelType) => void; + testingState: boolean; savingState: boolean; NotificationElement: React.ReactElement< unknown, @@ -109,11 +133,12 @@ interface FormAlertChannelsProps { >; title: string; initialValue: Store; - nameDisable?: boolean; + // editing indicates if the form is opened in edit mode + editing?: boolean; } FormAlertChannels.defaultProps = { - nameDisable: undefined, + editing: undefined, }; export default FormAlertChannels; diff --git a/frontend/src/container/GantChart/SpanLength/index.tsx b/frontend/src/container/GantChart/SpanLength/index.tsx index 4e27e6e624..4102757577 100644 --- a/frontend/src/container/GantChart/SpanLength/index.tsx +++ b/frontend/src/container/GantChart/SpanLength/index.tsx @@ -1,7 +1,4 @@ -import { - IIntervalUnit, - resolveTimeFromInterval, -} from 'container/TraceDetail/utils'; +import { convertTimeToRelevantUnit } from 'container/TraceDetail/utils'; import useThemeMode from 'hooks/useThemeMode'; import React from 'react'; import { toFixed } from 'utils/toFixed'; @@ -13,12 +10,12 @@ interface SpanLengthProps { leftOffset: string; bgColor: string; inMsCount: number; - intervalUnit: IIntervalUnit; } function SpanLength(props: SpanLengthProps): JSX.Element { - const { width, leftOffset, bgColor, intervalUnit, inMsCount } = props; + const { width, leftOffset, bgColor, inMsCount } = props; const { isDarkMode } = useThemeMode(); + const { time, timeUnitName } = convertTimeToRelevantUnit(inMsCount); return ( {`${toFixed( - resolveTimeFromInterval(inMsCount, intervalUnit), + time, 2, - )} ${intervalUnit.name}`} + )} ${timeUnitName}`} ); } diff --git a/frontend/src/container/GantChart/Trace/index.tsx b/frontend/src/container/GantChart/Trace/index.tsx index a0c01cc00e..db607092eb 100644 --- a/frontend/src/container/GantChart/Trace/index.tsx +++ b/frontend/src/container/GantChart/Trace/index.tsx @@ -153,7 +153,6 @@ function Trace(props: TraceProps): JSX.Element { width={width.toString()} bgColor={serviceColour} inMsCount={inMsCount / 1e6} - intervalUnit={intervalUnit} /> diff --git a/frontend/src/container/GeneralSettings/GeneralSettings.tsx b/frontend/src/container/GeneralSettings/GeneralSettings.tsx new file mode 100644 index 0000000000..10c9a1ac58 --- /dev/null +++ b/frontend/src/container/GeneralSettings/GeneralSettings.tsx @@ -0,0 +1,339 @@ +import { Button, Col, Modal, notification, Row, Typography } from 'antd'; +import setRetentionApi from 'api/settings/setRetention'; +import TextToolTip from 'components/TextToolTip'; +import useComponentPermission from 'hooks/useComponentPermission'; +import find from 'lodash-es/find'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { + IDiskType, + PayloadProps as GetDisksPayload, +} from 'types/api/disks/getDisks'; +import { PayloadProps as GetRetentionPayload } from 'types/api/settings/getRetention'; +import AppReducer from 'types/reducer/app'; + +import Retention from './Retention'; +import { ButtonContainer, ErrorText, ErrorTextContainer } from './styles'; + +type NumberOrNull = number | null; + +function GeneralSettings({ + ttlValuesPayload, + getAvailableDiskPayload, +}: GeneralSettingsProps): JSX.Element { + const { t } = useTranslation(); + const [modal, setModal] = useState(false); + const [postApiLoading, setPostApiLoading] = useState(false); + + const [availableDisks] = useState(getAvailableDiskPayload); + + const [currentTTLValues, setCurrentTTLValues] = useState(ttlValuesPayload); + const { role } = useSelector((state) => state.app); + + const [setRetentionPermission] = useComponentPermission( + ['set_retention_period'], + role, + ); + + const [ + metricsTotalRetentionPeriod, + setMetricsTotalRetentionPeriod, + ] = useState(null); + const [ + metricsS3RetentionPeriod, + setMetricsS3RetentionPeriod, + ] = useState(null); + const [ + tracesTotalRetentionPeriod, + setTracesTotalRetentionPeriod, + ] = useState(null); + const [ + tracesS3RetentionPeriod, + setTracesS3RetentionPeriod, + ] = useState(null); + + useEffect(() => { + if (currentTTLValues) { + setMetricsTotalRetentionPeriod(currentTTLValues.metrics_ttl_duration_hrs); + setMetricsS3RetentionPeriod( + currentTTLValues.metrics_move_ttl_duration_hrs + ? currentTTLValues.metrics_move_ttl_duration_hrs + : null, + ); + setTracesTotalRetentionPeriod(currentTTLValues.traces_ttl_duration_hrs); + setTracesS3RetentionPeriod( + currentTTLValues.traces_move_ttl_duration_hrs + ? currentTTLValues.traces_move_ttl_duration_hrs + : null, + ); + } + }, [currentTTLValues]); + + const onModalToggleHandler = (): void => { + setModal((modal) => !modal); + }; + + const onClickSaveHandler = useCallback(() => { + if (!setRetentionPermission) { + notification.error({ + message: `Sorry you don't have permission to make these changes`, + }); + return; + } + onModalToggleHandler(); + }, [setRetentionPermission]); + + const s3Enabled = useMemo( + () => !!find(availableDisks, (disks: IDiskType) => disks?.type === 's3'), + [availableDisks], + ); + + const renderConfig = [ + { + name: 'Metrics', + retentionFields: [ + { + name: t('settings.total_retention_period'), + value: metricsTotalRetentionPeriod, + setValue: setMetricsTotalRetentionPeriod, + }, + { + name: t('settings.move_to_s3'), + value: metricsS3RetentionPeriod, + setValue: setMetricsS3RetentionPeriod, + hide: !s3Enabled, + }, + ], + }, + { + name: 'Traces', + retentionFields: [ + { + name: t('settings.total_retention_period'), + value: tracesTotalRetentionPeriod, + setValue: setTracesTotalRetentionPeriod, + }, + { + name: t('settings.move_to_s3'), + value: tracesS3RetentionPeriod, + setValue: setTracesS3RetentionPeriod, + hide: !s3Enabled, + }, + ], + }, + ].map((category): JSX.Element | null => { + if ( + Array.isArray(category.retentionFields) && + category.retentionFields.length > 0 + ) { + return ( + + {category.name} + + {category.retentionFields.map((retentionField) => ( + + ))} + + ); + } + return null; + }); + + // eslint-disable-next-line sonarjs/cognitive-complexity + const onOkHandler = async (): Promise => { + try { + setPostApiLoading(true); + const apiCalls = []; + + if ( + !( + currentTTLValues?.metrics_move_ttl_duration_hrs === + metricsS3RetentionPeriod && + currentTTLValues.metrics_ttl_duration_hrs === metricsTotalRetentionPeriod + ) + ) { + apiCalls.push(() => + setRetentionApi({ + type: 'metrics', + totalDuration: `${metricsTotalRetentionPeriod || -1}h`, + coldStorage: s3Enabled ? 's3' : null, + toColdDuration: `${metricsS3RetentionPeriod || -1}h`, + }), + ); + } else { + apiCalls.push(() => Promise.resolve(null)); + } + + if ( + !( + currentTTLValues?.traces_move_ttl_duration_hrs === + tracesS3RetentionPeriod && + currentTTLValues.traces_ttl_duration_hrs === tracesTotalRetentionPeriod + ) + ) { + apiCalls.push(() => + setRetentionApi({ + type: 'traces', + totalDuration: `${tracesTotalRetentionPeriod || -1}h`, + coldStorage: s3Enabled ? 's3' : null, + toColdDuration: `${tracesS3RetentionPeriod || -1}h`, + }), + ); + } else { + apiCalls.push(() => Promise.resolve(null)); + } + const apiCallSequence = ['metrics', 'traces']; + const apiResponses = await Promise.all(apiCalls.map((api) => api())); + + apiResponses.forEach((apiResponse, idx) => { + const name = apiCallSequence[idx]; + if (apiResponse) { + if (apiResponse.statusCode === 200) { + notification.success({ + message: 'Success!', + placement: 'topRight', + + description: t('settings.retention_success_message', { name }), + }); + } else { + notification.error({ + message: 'Error', + description: t('settings.retention_error_message', { name }), + placement: 'topRight', + }); + } + } + }); + onModalToggleHandler(); + setPostApiLoading(false); + } catch (error) { + notification.error({ + message: 'Error', + description: t('settings.retention_failed_message'), + placement: 'topRight', + }); + } + // Updates the currentTTL Values in order to avoid pushing the same values. + setCurrentTTLValues({ + metrics_ttl_duration_hrs: metricsTotalRetentionPeriod || -1, + metrics_move_ttl_duration_hrs: metricsS3RetentionPeriod || -1, + traces_ttl_duration_hrs: tracesTotalRetentionPeriod || -1, + traces_move_ttl_duration_hrs: tracesS3RetentionPeriod || -1, + }); + + setModal(false); + }; + + // eslint-disable-next-line sonarjs/cognitive-complexity + const [isDisabled, errorText] = useMemo((): [boolean, string] => { + // Various methods to return dynamic error message text. + const messages = { + compareError: (name: string | number): string => + t('settings.retention_comparison_error', { name }), + nullValueError: (name: string | number): string => + t('settings.retention_null_value_error', { name }), + }; + + // Defaults to button not disabled and empty error message text. + let isDisabled = false; + let errorText = ''; + + if (s3Enabled) { + if ( + (metricsTotalRetentionPeriod || metricsS3RetentionPeriod) && + Number(metricsTotalRetentionPeriod) <= Number(metricsS3RetentionPeriod) + ) { + isDisabled = true; + errorText = messages.compareError('metrics'); + } else if ( + (tracesTotalRetentionPeriod || tracesS3RetentionPeriod) && + Number(tracesTotalRetentionPeriod) <= Number(tracesS3RetentionPeriod) + ) { + isDisabled = true; + errorText = messages.compareError('traces'); + } + } + + if (!metricsTotalRetentionPeriod || !tracesTotalRetentionPeriod) { + isDisabled = true; + if (!metricsTotalRetentionPeriod && !tracesTotalRetentionPeriod) { + errorText = messages.nullValueError('metrics and traces'); + } else if (!metricsTotalRetentionPeriod) { + errorText = messages.nullValueError('metrics'); + } else if (!tracesTotalRetentionPeriod) { + errorText = messages.nullValueError('traces'); + } + } + if ( + currentTTLValues?.metrics_ttl_duration_hrs === metricsTotalRetentionPeriod && + currentTTLValues.metrics_move_ttl_duration_hrs === + metricsS3RetentionPeriod && + currentTTLValues.traces_ttl_duration_hrs === tracesTotalRetentionPeriod && + currentTTLValues.traces_move_ttl_duration_hrs === tracesS3RetentionPeriod + ) { + isDisabled = true; + } + return [isDisabled, errorText]; + }, [ + currentTTLValues, + metricsS3RetentionPeriod, + metricsTotalRetentionPeriod, + s3Enabled, + t, + tracesS3RetentionPeriod, + tracesTotalRetentionPeriod, + ]); + + return ( + + {Element} + + + {errorText && {errorText}} + + + {renderConfig} + + + {t('settings.retention_confirmation_description')} + + + + + Save + + + + ); +} + +interface GeneralSettingsProps { + ttlValuesPayload: GetRetentionPayload; + getAvailableDiskPayload: GetDisksPayload; +} + +export default GeneralSettings; diff --git a/frontend/src/container/GeneralSettings/index.tsx b/frontend/src/container/GeneralSettings/index.tsx index 6a76282192..633c4822e6 100644 --- a/frontend/src/container/GeneralSettings/index.tsx +++ b/frontend/src/container/GeneralSettings/index.tsx @@ -1,331 +1,52 @@ -/* eslint-disable sonarjs/cognitive-complexity */ -import { Button, Col, Modal, notification, Row, Typography } from 'antd'; +import { Typography } from 'antd'; import getDisks from 'api/disks/getDisks'; import getRetentionPeriodApi from 'api/settings/getRetention'; -import setRetentionApi from 'api/settings/setRetention'; import Spinner from 'components/Spinner'; -import TextToolTip from 'components/TextToolTip'; -import useFetch from 'hooks/useFetch'; -import { find } from 'lodash-es'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React from 'react'; import { useTranslation } from 'react-i18next'; -import { IDiskType } from 'types/api/disks/getDisks'; -import { PayloadProps } from 'types/api/settings/getRetention'; +import { useQueries } from 'react-query'; -import Retention from './Retention'; -import { ButtonContainer, ErrorText, ErrorTextContainer } from './styles'; +import GeneralSettingsContainer from './GeneralSettings'; function GeneralSettings(): JSX.Element { - const { t } = useTranslation(); - const [notifications, Element] = notification.useNotification(); - const [modal, setModal] = useState(false); - const [postApiLoading, setPostApiLoading] = useState(false); - - const [availableDisks, setAvailableDisks] = useState(null); - - useEffect(() => { - getDisks().then((response) => setAvailableDisks(response.payload)); - }, []); - - const { payload, loading, error, errorMessage } = useFetch< - PayloadProps, - undefined - >(getRetentionPeriodApi, undefined); - - const [currentTTLValues, setCurrentTTLValues] = useState(payload); - - useEffect(() => { - setCurrentTTLValues(payload); - }, [payload]); - - const [metricsTotalRetentionPeriod, setMetricsTotalRetentionPeriod] = useState< - number | null - >(null); - const [metricsS3RetentionPeriod, setMetricsS3RetentionPeriod] = useState< - number | null - >(null); - const [tracesTotalRetentionPeriod, setTracesTotalRetentionPeriod] = useState< - number | null - >(null); - const [tracesS3RetentionPeriod, setTracesS3RetentionPeriod] = useState< - number | null - >(null); - - useEffect(() => { - if (currentTTLValues) { - setMetricsTotalRetentionPeriod(currentTTLValues.metrics_ttl_duration_hrs); - setMetricsS3RetentionPeriod( - currentTTLValues.metrics_move_ttl_duration_hrs - ? currentTTLValues.metrics_move_ttl_duration_hrs - : null, - ); - setTracesTotalRetentionPeriod(currentTTLValues.traces_ttl_duration_hrs); - setTracesS3RetentionPeriod( - currentTTLValues.traces_move_ttl_duration_hrs - ? currentTTLValues.traces_move_ttl_duration_hrs - : null, - ); - } - console.log({ changed: currentTTLValues }); - }, [currentTTLValues]); - - const onModalToggleHandler = (): void => { - setModal((modal) => !modal); - }; - - const onClickSaveHandler = useCallback(() => { - onModalToggleHandler(); - }, []); - - const s3Enabled = useMemo( - () => !!find(availableDisks, (disks: IDiskType) => disks?.type === 's3'), - [availableDisks], - ); - - const renderConfig = [ + const { t } = useTranslation('common'); + const [getRetentionPeriodApiResponse, getDisksResponse] = useQueries([ { - name: 'Metrics', - retentionFields: [ - { - name: t('settings.total_retention_period'), - value: metricsTotalRetentionPeriod, - setValue: setMetricsTotalRetentionPeriod, - }, - { - name: t('settings.move_to_s3'), - value: metricsS3RetentionPeriod, - setValue: setMetricsS3RetentionPeriod, - hide: !s3Enabled, - }, - ], + queryFn: getRetentionPeriodApi, + queryKey: 'getRetentionPeriodApi', }, { - name: 'Traces', - retentionFields: [ - { - name: t('settings.total_retention_period'), - value: tracesTotalRetentionPeriod, - setValue: setTracesTotalRetentionPeriod, - }, - { - name: t('settings.move_to_s3'), - value: tracesS3RetentionPeriod, - setValue: setTracesS3RetentionPeriod, - hide: !s3Enabled, - }, - ], + queryFn: getDisks, + queryKey: 'getDisks', }, - ].map((category): JSX.Element | null => { - if ( - Array.isArray(category.retentionFields) && - category.retentionFields.length > 0 - ) { - return ( - - {category.name} - - {category.retentionFields.map((retentionField) => ( - - ))} - - ); - } - return null; - }); - - const onOkHandler = async (): Promise => { - try { - setPostApiLoading(true); - const apiCalls = []; - - if ( - !( - currentTTLValues?.metrics_move_ttl_duration_hrs === - metricsS3RetentionPeriod && - currentTTLValues.metrics_ttl_duration_hrs === metricsTotalRetentionPeriod - ) - ) { - apiCalls.push(() => - setRetentionApi({ - type: 'metrics', - totalDuration: `${metricsTotalRetentionPeriod || -1}h`, - coldStorage: s3Enabled ? 's3' : null, - toColdDuration: `${metricsS3RetentionPeriod || -1}h`, - }), - ); - } else { - apiCalls.push(() => Promise.resolve(null)); - } - - if ( - !( - currentTTLValues?.traces_move_ttl_duration_hrs === - tracesS3RetentionPeriod && - currentTTLValues.traces_ttl_duration_hrs === tracesTotalRetentionPeriod - ) - ) { - apiCalls.push(() => - setRetentionApi({ - type: 'traces', - totalDuration: `${tracesTotalRetentionPeriod || -1}h`, - coldStorage: s3Enabled ? 's3' : null, - toColdDuration: `${tracesS3RetentionPeriod || -1}h`, - }), - ); - } else { - apiCalls.push(() => Promise.resolve(null)); - } - const apiCallSequence = ['metrics', 'traces']; - const apiResponses = await Promise.all(apiCalls.map((api) => api())); - - apiResponses.forEach((apiResponse, idx) => { - const name = apiCallSequence[idx]; - if (apiResponse) { - if (apiResponse.statusCode === 200) { - notifications.success({ - message: 'Success!', - placement: 'topRight', - - description: t('settings.retention_success_message', { name }), - }); - } else { - notifications.error({ - message: 'Error', - description: t('settings.retention_error_message', { name }), - placement: 'topRight', - }); - } - } - }); - onModalToggleHandler(); - setPostApiLoading(false); - } catch (error) { - notifications.error({ - message: 'Error', - description: t('settings.retention_failed_message'), - placement: 'topRight', - }); - } - // Updates the currentTTL Values in order to avoid pushing the same values. - setCurrentTTLValues({ - metrics_ttl_duration_hrs: metricsTotalRetentionPeriod || -1, - metrics_move_ttl_duration_hrs: metricsS3RetentionPeriod || -1, - traces_ttl_duration_hrs: tracesTotalRetentionPeriod || -1, - traces_move_ttl_duration_hrs: tracesS3RetentionPeriod || -1, - }); - - setModal(false); - }; - - const [isDisabled, errorText] = useMemo((): [boolean, string] => { - // Various methods to return dynamic error message text. - const messages = { - compareError: (name: string | number): string => - t('settings.retention_comparison_error', { name }), - nullValueError: (name: string | number): string => - t('settings.retention_null_value_error', { name }), - }; - - // Defaults to button not disabled and empty error message text. - let isDisabled = false; - let errorText = ''; - - if (s3Enabled) { - if ( - (metricsTotalRetentionPeriod || metricsS3RetentionPeriod) && - Number(metricsTotalRetentionPeriod) <= Number(metricsS3RetentionPeriod) - ) { - isDisabled = true; - errorText = messages.compareError('metrics'); - } else if ( - (tracesTotalRetentionPeriod || tracesS3RetentionPeriod) && - Number(tracesTotalRetentionPeriod) <= Number(tracesS3RetentionPeriod) - ) { - isDisabled = true; - errorText = messages.compareError('traces'); - } - } - - if (!metricsTotalRetentionPeriod || !tracesTotalRetentionPeriod) { - isDisabled = true; - if (!metricsTotalRetentionPeriod && !tracesTotalRetentionPeriod) { - errorText = messages.nullValueError('metrics and traces'); - } else if (!metricsTotalRetentionPeriod) { - errorText = messages.nullValueError('metrics'); - } else if (!tracesTotalRetentionPeriod) { - errorText = messages.nullValueError('traces'); - } - } - if ( - currentTTLValues?.metrics_ttl_duration_hrs === metricsTotalRetentionPeriod && - currentTTLValues.metrics_move_ttl_duration_hrs === - metricsS3RetentionPeriod && - currentTTLValues.traces_ttl_duration_hrs === tracesTotalRetentionPeriod && - currentTTLValues.traces_move_ttl_duration_hrs === tracesS3RetentionPeriod - ) { - isDisabled = true; - } - return [isDisabled, errorText]; - }, [ - currentTTLValues, - metricsS3RetentionPeriod, - metricsTotalRetentionPeriod, - s3Enabled, - t, - tracesS3RetentionPeriod, - tracesTotalRetentionPeriod, ]); - if (error) { - return {errorMessage}; + if (getRetentionPeriodApiResponse.isError || getDisksResponse.isError) { + return ( + + {getRetentionPeriodApiResponse.data?.error || + getDisksResponse.data?.error || + t('something_went_wrong')} + + ); } - if (loading || currentTTLValues === undefined) { + if ( + getRetentionPeriodApiResponse.isLoading || + getDisksResponse.isLoading || + !getDisksResponse.data?.payload || + !getRetentionPeriodApiResponse.data?.payload + ) { return ; } return ( - - {Element} - - - {errorText && {errorText}} - - - {renderConfig} - - - {t('settings.retention_confirmation_description')} - - - - - Save - - - + ); } diff --git a/frontend/src/container/GridGraphComponent/index.tsx b/frontend/src/container/GridGraphComponent/index.tsx index 9725957a43..d2139b1a08 100644 --- a/frontend/src/container/GridGraphComponent/index.tsx +++ b/frontend/src/container/GridGraphComponent/index.tsx @@ -60,7 +60,9 @@ function GridGraphComponent({ diff --git a/frontend/src/container/GridGraphLayout/Graph/Bar/index.tsx b/frontend/src/container/GridGraphLayout/Graph/Bar/index.tsx deleted file mode 100644 index 7214d4839e..0000000000 --- a/frontend/src/container/GridGraphLayout/Graph/Bar/index.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { - DeleteOutlined, - EditFilled, - FullscreenOutlined, -} from '@ant-design/icons'; -import history from 'lib/history'; -import React from 'react'; -import { Widgets } from 'types/api/dashboard/getAll'; - -import { Container } from './styles'; - -function Bar({ - widget, - onViewFullScreenHandler, - onDeleteHandler, -}: BarProps): JSX.Element { - const onEditHandler = (): void => { - const widgetId = widget.id; - history.push( - `${window.location.pathname}/new?widgetId=${widgetId}&graphType=${widget.panelTypes}`, - ); - }; - - return ( - - - - - - ); -} - -interface BarProps { - widget: Widgets; - onViewFullScreenHandler: () => void; - onDeleteHandler: () => void; -} - -export default Bar; diff --git a/frontend/src/container/GridGraphLayout/Graph/Bar/styles.ts b/frontend/src/container/GridGraphLayout/Graph/Bar/styles.ts deleted file mode 100644 index ca8073a672..0000000000 --- a/frontend/src/container/GridGraphLayout/Graph/Bar/styles.ts +++ /dev/null @@ -1,15 +0,0 @@ -import styled from 'styled-components'; - -export const Container = styled.div` - height: 15%; - align-items: center; - justify-content: flex-end; - display: flex; - gap: 1rem; - padding-right: 1rem; - padding-left: 1rem; - padding-top: 0.5rem; - position: absolute; - top: 0; - right: 0; -`; diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx index ba8a66a82d..ef04a4e3da 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/index.tsx @@ -116,16 +116,11 @@ function FullView({ })); } else { const chartDataSet = getChartData({ - queryData: { - data: response.map((e) => ({ - query: e.query, - legend: e.legend, - queryData: e.queryData.payload?.result || [], - })), - error: false, - errorMessage: '', - loading: false, - }, + queryData: response.map((e) => ({ + query: e.query, + legend: e.legend, + queryData: e.queryData.payload?.result || [], + })), }); setState((state) => ({ diff --git a/frontend/src/container/GridGraphLayout/Graph/index.tsx b/frontend/src/container/GridGraphLayout/Graph/index.tsx index 0447cfca94..fa87b67638 100644 --- a/frontend/src/container/GridGraphLayout/Graph/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/index.tsx @@ -20,7 +20,7 @@ import AppActions from 'types/actions'; import { GlobalTime } from 'types/actions/globalTime'; import { Widgets } from 'types/api/dashboard/getAll'; -import Bar from './Bar'; +import WidgetHeader from '../WidgetHeader'; import FullView from './FullView'; import { ErrorContainer, FullViewContainer, Modal } from './styles'; @@ -37,6 +37,7 @@ function GridCardGraph({ error: false, payload: undefined, }); + const [hovered, setHovered] = useState(false); const [modal, setModal] = useState(false); const { minTime, maxTime } = useSelector( (state) => state.globalTime, @@ -88,16 +89,11 @@ function GridCardGraph({ })); } else { const chartDataSet = getChartData({ - queryData: { - data: response.map((e) => ({ - query: e.query, - legend: e.legend, - queryData: e.queryData.payload?.result || [], - })), - error: false, - errorMessage: '', - loading: false, - }, + queryData: response.map((e) => ({ + query: e.query, + legend: e.legend, + queryData: e.queryData.payload?.result || [], + })), }); setState((state) => ({ @@ -171,10 +167,12 @@ function GridCardGraph({ return ( <> {getModals()} - onToggleModal(setModal)} + onToggleModal(setDeletModal)} + onView={(): void => onToggleModal(setModal)} + onDelete={(): void => onToggleModal(setDeletModal)} /> {state.errorMessage} @@ -187,11 +185,26 @@ function GridCardGraph({ } return ( - <> - onToggleModal(setModal)} + { + setHovered(true); + }} + onFocus={(): void => { + setHovered(true); + }} + onMouseOut={(): void => { + setHovered(false); + }} + onBlur={(): void => { + setHovered(false); + }} + > + onToggleModal(setDeletModal)} + onView={(): void => onToggleModal(setModal)} + onDelete={(): void => onToggleModal(setDeletModal)} /> {getModals()} @@ -202,12 +215,12 @@ function GridCardGraph({ data: state.payload, isStacked: widget.isStacked, opacity: widget.opacity, - title: widget.title, + title: ' ', // empty title to accommodate absolutely positioned widget header name, yAxisUnit, }} /> - > + ); } diff --git a/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx b/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx new file mode 100644 index 0000000000..a627bac3e1 --- /dev/null +++ b/frontend/src/container/GridGraphLayout/WidgetHeader/index.tsx @@ -0,0 +1,127 @@ +import { + DeleteOutlined, + DownOutlined, + EditFilled, + FullscreenOutlined, +} from '@ant-design/icons'; +import { Dropdown, Menu, Typography } from 'antd'; +import useComponentPermission from 'hooks/useComponentPermission'; +import history from 'lib/history'; +import React, { useState } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { Widgets } from 'types/api/dashboard/getAll'; +import AppReducer from 'types/reducer/app'; + +import { + ArrowContainer, + HeaderContainer, + HeaderContentContainer, + MenuItemContainer, +} from './styles'; + +type TWidgetOptions = 'view' | 'edit' | 'delete' | string; +interface IWidgetHeaderProps { + title: string; + widget: Widgets; + onView: VoidFunction; + onDelete: VoidFunction; + parentHover: boolean; +} +function WidgetHeader({ + title, + widget, + onView, + onDelete, + parentHover, +}: IWidgetHeaderProps): JSX.Element { + const [localHover, setLocalHover] = useState(false); + + const onEditHandler = (): void => { + const widgetId = widget.id; + history.push( + `${window.location.pathname}/new?widgetId=${widgetId}&graphType=${widget.panelTypes}`, + ); + }; + + const keyMethodMapping: { + [K in TWidgetOptions]: { key: TWidgetOptions; method: VoidFunction }; + } = { + view: { + key: 'view', + method: onView, + }, + edit: { + key: 'edit', + method: onEditHandler, + }, + delete: { + key: 'delete', + method: onDelete, + }, + }; + const onMenuItemSelectHandler = ({ key }: { key: TWidgetOptions }): void => { + keyMethodMapping[key]?.method(); + }; + const { role } = useSelector((state) => state.app); + + const [deleteWidget, editWidget] = useComponentPermission( + ['delete_widget', 'edit_widget'], + role, + ); + + const menu = ( + + + + View + + + + {editWidget && ( + + + Edit + + + )} + + {deleteWidget && ( + <> + + + + Delete + + + > + )} + + ); + + return ( + + setLocalHover(true)} + onMouseOut={(): void => setLocalHover(false)} + hover={localHover} + > + e.preventDefault()}> + + {title} + + + + + + + + ); +} + +export default WidgetHeader; diff --git a/frontend/src/container/GridGraphLayout/WidgetHeader/styles.ts b/frontend/src/container/GridGraphLayout/WidgetHeader/styles.ts new file mode 100644 index 0000000000..9600a6bcb4 --- /dev/null +++ b/frontend/src/container/GridGraphLayout/WidgetHeader/styles.ts @@ -0,0 +1,30 @@ +import { grey } from '@ant-design/colors'; +import styled from 'styled-components'; + +export const MenuItemContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; +`; + +export const HeaderContainer = styled.div<{ hover: boolean }>` + width: 100%; + text-align: center; + background: ${({ hover }): string => (hover ? `${grey[0]}66` : 'inherit')}; + padding: 0.25rem 0; + font-size: 0.8rem; + cursor: all-scroll; + position: absolute; +`; + +export const HeaderContentContainer = styled.span` + cursor: pointer; + position: relative; + text-align: center; +`; + +export const ArrowContainer = styled.span<{ hover: boolean }>` + visibility: ${({ hover }): string => (hover ? 'visible' : 'hidden')}; + position: absolute; + right: -1rem; +`; diff --git a/frontend/src/container/GridGraphLayout/index.tsx b/frontend/src/container/GridGraphLayout/index.tsx index e5676c8f7b..48d51fd7f0 100644 --- a/frontend/src/container/GridGraphLayout/index.tsx +++ b/frontend/src/container/GridGraphLayout/index.tsx @@ -1,15 +1,15 @@ -/* eslint-disable react/jsx-no-useless-fragment */ /* eslint-disable react/no-unstable-nested-components */ -/* eslint-disable react/display-name */ import { SaveFilled } from '@ant-design/icons'; import { notification } from 'antd'; import updateDashboardApi from 'api/dashboard/update'; import Spinner from 'components/Spinner'; import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; +import useComponentPermission from 'hooks/useComponentPermission'; import React, { memo, useCallback, useEffect, useRef, useState } from 'react'; import { Layout } from 'react-grid-layout'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; +import AppReducer from 'types/reducer/app'; import DashboardReducer from 'types/reducer/dashboards'; import { v4 } from 'uuid'; @@ -44,6 +44,9 @@ function GridGraph(): JSX.Element { const isMounted = useRef(true); const isDeleted = useRef(false); + const { role } = useSelector((state) => state.app); + + const [saveLayout] = useComponentPermission(['save_layout'], role); const getPreLayouts: () => LayoutProps[] = useCallback(() => { if (widgets === undefined) { @@ -85,7 +88,7 @@ function GridGraph(): JSX.Element { /> ); } - return <>>; + return ; }, })); }, [widgets, data.layout]); @@ -150,6 +153,7 @@ function GridGraph(): JSX.Element { w: e.i === '__dropping-elem__' ? 6 : e.w, h: e.i === '__dropping-elem__' ? 2 : e.h, })) + // removing add widgets layout config .filter((e) => e.maxW === undefined), }); } catch (error) { @@ -172,13 +176,15 @@ function GridGraph(): JSX.Element { })); const response = await updateDashboardApi({ - title: data.title, + data: { + title: data.title, + description: data.description, + name: data.name, + tags: data.tags, + widgets: data.widgets, + layout: saveLayoutState.payload.filter((e) => e.maxW === undefined), + }, uuid: selectedDashboard.uuid, - description: data.description, - name: data.name, - tags: data.tags, - widgets: data.widgets, - layout: saveLayoutState.payload.filter((e) => e.maxW === undefined), }); if (response.statusCode === 200) { setSaveLayoutState((state) => ({ @@ -212,16 +218,18 @@ function GridGraph(): JSX.Element { return ( <> - - } - danger={saveLayoutState.error} - > - Save Layout - - + {saveLayout && ( + + } + danger={saveLayoutState.error} + > + Save Layout + + + )} => { const response = await updateDashboardApi({ - title: data.title, - uuid: selectedDashboard.uuid, - description: data.description, - name: data.name, - tags: data.tags, - widgets: [ - ...(data.widgets || []), - { - description: '', - id: generateWidgetId, - isStacked: false, - nullZeroValues: '', - opacity: '', - panelTypes: graphType, - query: [ - { - query: '', - legend: '', + data: { + title: data.title, + description: data.description, + name: data.name, + tags: data.tags, + widgets: [ + ...(data.widgets || []), + { + description: '', + id: generateWidgetId, + isStacked: false, + nullZeroValues: '', + opacity: '', + panelTypes: graphType, + query: [ + { + query: '', + legend: '', + }, + ], + queryData: { + data: [], + error: false, + errorMessage: '', + loading: false, }, - ], - queryData: { - data: [], - error: false, - errorMessage: '', - loading: false, + timePreferance: 'GLOBAL_TIME', + title: '', }, - timePreferance: 'GLOBAL_TIME', - title: '', - }, - ], - layout, + ], + layout, + }, + uuid: selectedDashboard.uuid, }); if (response.statusCode === 200) { diff --git a/frontend/src/container/Header/CurrentOrganization/index.tsx b/frontend/src/container/Header/CurrentOrganization/index.tsx new file mode 100644 index 0000000000..7b32c3f913 --- /dev/null +++ b/frontend/src/container/Header/CurrentOrganization/index.tsx @@ -0,0 +1,79 @@ +import { PlusSquareOutlined } from '@ant-design/icons'; +import { Avatar, Typography } from 'antd'; +import { INVITE_MEMBERS_HASH } from 'constants/app'; +import ROUTES from 'constants/routes'; +import useComponentPermission from 'hooks/useComponentPermission'; +import history from 'lib/history'; +import React from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import AppReducer from 'types/reducer/app'; + +import { + InviteMembersContainer, + OrganizationContainer, + OrganizationWrapper, +} from '../styles'; + +function CurrentOrganization({ + onToggle, +}: CurrentOrganizationProps): JSX.Element { + const { org, role } = useSelector((state) => state.app); + const [currentOrgSettings, inviteMembers] = useComponentPermission( + ['current_org_settings', 'invite_members'], + role, + ); + + // just to make sure role and org are present in the reducer + if (!org || !role) { + return ; + } + + const orgName = org[0].name; + + return ( + <> + CURRENT ORGANIZATION + + + + + {orgName} + + {orgName} + + + {currentOrgSettings && ( + { + onToggle(); + history.push(ROUTES.ORG_SETTINGS); + }} + > + Settings + + )} + + + {inviteMembers && ( + + + { + onToggle(); + history.push(`${ROUTES.ORG_SETTINGS}${INVITE_MEMBERS_HASH}`); + }} + > + Invite Members + + + )} + > + ); +} + +interface CurrentOrganizationProps { + onToggle: VoidFunction; +} + +export default CurrentOrganization; diff --git a/frontend/src/container/Header/SignedInAs/index.tsx b/frontend/src/container/Header/SignedInAs/index.tsx new file mode 100644 index 0000000000..a9fab7507a --- /dev/null +++ b/frontend/src/container/Header/SignedInAs/index.tsx @@ -0,0 +1,45 @@ +import { Avatar, Typography } from 'antd'; +import ROUTES from 'constants/routes'; +import history from 'lib/history'; +import React from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import AppReducer from 'types/reducer/app'; + +import { AvatarContainer, ManageAccountLink, Wrapper } from '../styles'; + +function SignedInAS(): JSX.Element { + const { user } = useSelector((state) => state.app); + + if (!user) { + return ; + } + + const { name, email } = user; + + return ( + + SIGNED IN AS + + + + {name[0]} + + + {name} + {email} + + + { + history.push(ROUTES.MY_SETTINGS); + }} + > + Manage Account + + + + ); +} + +export default SignedInAS; diff --git a/frontend/src/container/Header/index.tsx b/frontend/src/container/Header/index.tsx index b78275e28b..c3c9eb8e84 100644 --- a/frontend/src/container/Header/index.tsx +++ b/frontend/src/container/Header/index.tsx @@ -1,49 +1,165 @@ -import { Col } from 'antd'; +import { + CaretDownFilled, + CaretUpFilled, + LogoutOutlined, +} from '@ant-design/icons'; +import { + Avatar, + Divider, + Dropdown, + Layout, + Menu, + Space, + Typography, +} from 'antd'; +import { Logout } from 'api/utils'; import ROUTES from 'constants/routes'; -import history from 'lib/history'; -import React from 'react'; -import { matchPath } from 'react-router-dom'; +import setTheme, { AppMode } from 'lib/theme/setTheme'; +import React, { useCallback, useState } from 'react'; +import { connect, useSelector } from 'react-redux'; +import { NavLink } from 'react-router-dom'; +import { bindActionCreators } from 'redux'; +import { ThunkDispatch } from 'redux-thunk'; +import { ToggleDarkMode } from 'store/actions'; +import { AppState } from 'store/reducers'; +import AppActions from 'types/actions'; +import AppReducer from 'types/reducer/app'; -import ShowBreadcrumbs from './Breadcrumbs'; -import DateTimeSelector from './DateTimeSelection'; -import { Container } from './styles'; +import CurrentOrganization from './CurrentOrganization'; +import SignedInAS from './SignedInAs'; +import { + Container, + LogoutContainer, + MenuContainer, + ToggleButton, +} from './styles'; -const routesToSkip = [ - ROUTES.SETTINGS, - ROUTES.LIST_ALL_ALERT, - ROUTES.TRACE_DETAIL, - ROUTES.ALL_CHANNELS, -]; +function HeaderContainer({ toggleDarkMode }: Props): JSX.Element { + const { isDarkMode, user, currentVersion } = useSelector( + (state) => state.app, + ); + const [isUserDropDownOpen, setIsUserDropDownOpen] = useState(); -function TopNav(): JSX.Element | null { - if (history.location.pathname === ROUTES.SIGN_UP) { - return null; - } + const onToggleThemeHandler = useCallback(() => { + const preMode: AppMode = isDarkMode ? 'lightMode' : 'darkMode'; + setTheme(preMode); - const checkRouteExists = (currentPath: string): boolean => { - for (let i = 0; i < routesToSkip.length; i += 1) { - if ( - matchPath(currentPath, { path: routesToSkip[i], exact: true, strict: true }) - ) { - return true; - } - } - return false; + const id: AppMode = preMode; + const { head } = document; + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.type = 'text/css'; + link.href = !isDarkMode ? '/css/antd.dark.min.css' : '/css/antd.min.css'; + link.media = 'all'; + link.id = id; + head.appendChild(link); + + link.onload = (): void => { + toggleDarkMode(); + const prevNode = document.getElementById('appMode'); + prevNode?.remove(); + }; + }, [toggleDarkMode, isDarkMode]); + + const onArrowClickHandler: VoidFunction = () => { + setIsUserDropDownOpen((state) => !state); }; - return ( - - - - + const onClickLogoutHandler = (): void => { + Logout(); + }; - {!checkRouteExists(history.location.pathname) && ( - - - - )} - + const menu = ( + + + + + + + + + { + if (e.key === 'Enter' || e.key === 'Space') { + onClickLogoutHandler(); + } + }} + role="button" + onClick={onClickLogoutHandler} + > + Logout + + + + + ); + + return ( + + + + + + SigNoz + + + + + + + + {user?.name[0]} + {!isUserDropDownOpen ? ( + + ) : ( + + )} + + + + + ); } -export default TopNav; +interface DispatchProps { + toggleDarkMode: () => void; +} + +const mapDispatchToProps = ( + dispatch: ThunkDispatch, +): DispatchProps => ({ + toggleDarkMode: bindActionCreators(ToggleDarkMode, dispatch), +}); + +type Props = DispatchProps; + +export default connect(null, mapDispatchToProps)(HeaderContainer); diff --git a/frontend/src/container/Header/styles.ts b/frontend/src/container/Header/styles.ts index feda027d24..6355c0c8c7 100644 --- a/frontend/src/container/Header/styles.ts +++ b/frontend/src/container/Header/styles.ts @@ -1,9 +1,67 @@ -import { Row } from 'antd'; +import { Menu, Switch, Typography } from 'antd'; import styled from 'styled-components'; -export const Container = styled(Row)` +export const Container = styled.div` + display: flex; + justify-content: space-between; +`; + +export const AvatarContainer = styled.div` + display: flex; + gap: 1rem; +`; + +export const Wrapper = styled.div` + display: flex; + justify-content: space-between; + margin-top: 1rem; +`; + +export const ManageAccountLink = styled(Typography.Link)` + width: 6rem; + text-align: end; +`; + +export const OrganizationWrapper = styled.div` + display: flex; + gap: 1rem; + align-items: center; + margin-top: 1rem; +`; + +export const OrganizationContainer = styled.div` + display: flex; + justify-content: space-between; + align-items: center; +`; + +export const InviteMembersContainer = styled.div` + display: flex; + gap: 0.5rem; + align-items: center; + margin-top: 1.25rem; +`; + +export const LogoutContainer = styled.div` + display: flex; + gap: 0.5rem; + align-items: center; +`; + +export const MenuContainer = styled(Menu)` + padding: 1rem; +`; + +export interface DarkModeProps { + checked?: boolean; + defaultChecked?: boolean; +} + +export const ToggleButton = styled(Switch)` &&& { - margin-top: 2rem; - min-height: 8vh; + background: ${({ checked }): string => (checked === false ? 'grey' : '')}; + } + .ant-switch-inner { + font-size: 1rem !important; } `; diff --git a/frontend/src/container/IsRouteAccessible/index.tsx b/frontend/src/container/IsRouteAccessible/index.tsx new file mode 100644 index 0000000000..3e3b246288 --- /dev/null +++ b/frontend/src/container/IsRouteAccessible/index.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +function IsRouteAccessible(): JSX.Element { + return asd; +} + +export default IsRouteAccessible; diff --git a/frontend/src/container/ListAlertRules/ListAlert.tsx b/frontend/src/container/ListAlertRules/ListAlert.tsx index 5bff7041c8..8ec1fa9987 100644 --- a/frontend/src/container/ListAlertRules/ListAlert.tsx +++ b/frontend/src/container/ListAlertRules/ListAlert.tsx @@ -2,28 +2,44 @@ import { PlusOutlined } from '@ant-design/icons'; import { notification, Tag, Typography } from 'antd'; import Table, { ColumnsType } from 'antd/lib/table'; -import getAll from 'api/alerts/getAll'; import TextToolTip from 'components/TextToolTip'; import ROUTES from 'constants/routes'; +import useComponentPermission from 'hooks/useComponentPermission'; import useInterval from 'hooks/useInterval'; import history from 'lib/history'; import React, { useCallback, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { UseQueryResult } from 'react-query'; +import { useSelector } from 'react-redux'; import { generatePath } from 'react-router-dom'; +import { AppState } from 'store/reducers'; +import { ErrorResponse, SuccessResponse } from 'types/api'; import { Alerts } from 'types/api/alerts/getAll'; +import AppReducer from 'types/reducer/app'; import DeleteAlert from './DeleteAlert'; import { Button, ButtonContainer } from './styles'; import Status from './TableComponents/Status'; -function ListAlert({ allAlertRules }: ListAlertProps): JSX.Element { +function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element { const [data, setData] = useState(allAlertRules || []); + const { t } = useTranslation('common'); + const { role } = useSelector((state) => state.app); + const [addNewAlert, action] = useComponentPermission( + ['add_new_alert', 'action'], + role, + ); useInterval(() => { (async (): Promise => { - const { payload, statusCode } = await getAll(); - - if (statusCode === 200 && payload !== null) { - setData(payload); + const { data: refetchData, status } = await refetch(); + if (status === 'success') { + setData(refetchData?.payload || []); + } + if (status === 'error') { + notification.error({ + message: t('something_went_wrong'), + }); } })(); }, 30000); @@ -55,7 +71,7 @@ function ListAlert({ allAlertRules }: ListAlertProps): JSX.Element { title: 'Alert Name', dataIndex: 'name', key: 'name', - sorter: (a, b): number => a.name.length - b.name.length, + sorter: (a, b): number => a.name.charCodeAt(0) - b.name.charCodeAt(0), }, { title: 'Severity', @@ -76,14 +92,6 @@ function ListAlert({ allAlertRules }: ListAlertProps): JSX.Element { dataIndex: 'labels', key: 'tags', align: 'center', - sorter: (a, b): number => { - const alength = Object.keys(a.labels).filter((e) => e !== 'severity') - .length; - const blength = Object.keys(b.labels).filter((e) => e !== 'severity') - .length; - - return blength - alength; - }, render: (value): JSX.Element => { const objectKeys = Object.keys(value); const withOutSeverityKeys = objectKeys.filter((e) => e !== 'severity'); @@ -105,7 +113,10 @@ function ListAlert({ allAlertRules }: ListAlertProps): JSX.Element { ); }, }, - { + ]; + + if (action) { + columns.push({ title: 'Action', dataIndex: 'id', key: 'action', @@ -117,12 +128,11 @@ function ListAlert({ allAlertRules }: ListAlertProps): JSX.Element { onEditHandler(id.toString())} type="link"> Edit - {/* Pause */} > ); }, - }, - ]; + }); + } return ( <> @@ -136,9 +146,11 @@ function ListAlert({ allAlertRules }: ListAlertProps): JSX.Element { }} /> - }> - New Alert - + {addNewAlert && ( + }> + New Alert + + )} @@ -148,6 +160,7 @@ function ListAlert({ allAlertRules }: ListAlertProps): JSX.Element { interface ListAlertProps { allAlertRules: Alerts[]; + refetch: UseQueryResult>['refetch']; } export default ListAlert; diff --git a/frontend/src/container/ListAlertRules/index.tsx b/frontend/src/container/ListAlertRules/index.tsx index ce44e871e9..abcf1efe58 100644 --- a/frontend/src/container/ListAlertRules/index.tsx +++ b/frontend/src/container/ListAlertRules/index.tsx @@ -1,29 +1,31 @@ import getAll from 'api/alerts/getAll'; import Spinner from 'components/Spinner'; -import useFetch from 'hooks/useFetch'; import React from 'react'; -import { PayloadProps } from 'types/api/alerts/getAll'; +import { useTranslation } from 'react-i18next'; +import { useQuery } from 'react-query'; import ListAlert from './ListAlert'; function ListAlertRules(): JSX.Element { - const { loading, payload, error, errorMessage } = useFetch< - PayloadProps, - undefined - >(getAll); + const { t } = useTranslation('common'); + const { data, isError, isLoading, refetch } = useQuery('allAlerts', { + queryFn: getAll, + cacheTime: 0, + }); - if (error) { - return {errorMessage}; + if (isError) { + return {data?.error || t('something_went_wrong')}; } - if (loading || payload === undefined) { + if (isLoading || !data?.payload) { return ; } return ( ); diff --git a/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx b/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx new file mode 100644 index 0000000000..24b2d8b753 --- /dev/null +++ b/frontend/src/container/ListOfDashboard/ImportJSON/index.tsx @@ -0,0 +1,182 @@ +import { red } from '@ant-design/colors'; +import { ExclamationCircleTwoTone } from '@ant-design/icons'; +import { + Button, + Modal, + notification, + Space, + Typography, + Upload, + UploadProps, +} from 'antd'; +import createDashboard from 'api/dashboard/create'; +import Editor from 'components/Editor'; +import ROUTES from 'constants/routes'; +import history from 'lib/history'; +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { generatePath } from 'react-router-dom'; +import { DashboardData } from 'types/api/dashboard/getAll'; + +import { EditorContainer, FooterContainer } from './styles'; + +function ImportJSON({ + isImportJSONModalVisible, + onModalHandler, +}: ImportJSONProps): JSX.Element { + const [jsonData, setJsonData] = useState>(); + const { t } = useTranslation(['dashboard', 'common']); + const [isUploadJSONError, setIsUploadJSONError] = useState(false); + const [isCreateDashboardError, setIsCreateDashboardError] = useState( + false, + ); + const [dashboardCreating, setDashboardCreating] = useState(false); + + const [editorValue, setEditorValue] = useState(''); + + const onChangeHandler: UploadProps['onChange'] = (info) => { + const { fileList } = info; + const reader = new FileReader(); + + const lastFile = fileList[fileList.length - 1]; + + if (lastFile.originFileObj) { + reader.onload = async (event): Promise => { + if (event.target) { + const target = event.target.result; + try { + if (target) { + const targetFile = target.toString(); + const parsedValue = JSON.parse(targetFile); + setJsonData(parsedValue); + setEditorValue(JSON.stringify(parsedValue, null, 2)); + setIsUploadJSONError(false); + } + } catch (error) { + setIsUploadJSONError(true); + } + } + }; + reader.readAsText(lastFile.originFileObj); + } + }; + + const onClickLoadJsonHandler = async (): Promise => { + try { + setDashboardCreating(true); + const dashboardData = JSON.parse(editorValue) as DashboardData; + + // removing the queryData + const parsedWidgets: DashboardData = { + ...dashboardData, + widgets: dashboardData.widgets?.map((e) => ({ + ...e, + queryData: { + ...e.queryData, + data: e.queryData.data.map((queryData) => ({ + ...queryData, + queryData: [], + })), + }, + })), + }; + + const response = await createDashboard({ + ...parsedWidgets, + }); + + if (response.statusCode === 200) { + setTimeout(() => { + history.push( + generatePath(ROUTES.DASHBOARD, { + dashboardId: response.payload.uuid, + }), + ); + }, 10); + } else { + setIsCreateDashboardError(true); + notification.error({ + message: + response.error || + t('something_went_wrong', { + ns: 'common', + }), + }); + } + setDashboardCreating(false); + } catch { + setDashboardCreating(false); + + setIsCreateDashboardError(true); + } + }; + + const getErrorNode = (error: string): JSX.Element => ( + + + {error} + + ); + + return ( + + {t('import_json')} + {t('import_dashboard_by_pasting')} + > + } + footer={ + + + {t('load_json')} + + {isCreateDashboardError && getErrorNode(t('error_loading_json'))} + + } + > + + + false} + action="none" + data={jsonData} + > + {t('upload_json_file')} + + {isUploadJSONError && <>{getErrorNode(t('error_upload_json'))}>} + + + + {t('paste_json_below')} + setEditorValue(newValue)} + value={editorValue} + language="json" + /> + + + + ); +} + +interface ImportJSONProps { + isImportJSONModalVisible: boolean; + onModalHandler: VoidFunction; +} + +export default ImportJSON; diff --git a/frontend/src/container/ListOfDashboard/ImportJSON/styles.ts b/frontend/src/container/ListOfDashboard/ImportJSON/styles.ts new file mode 100644 index 0000000000..7dd936baef --- /dev/null +++ b/frontend/src/container/ListOfDashboard/ImportJSON/styles.ts @@ -0,0 +1,10 @@ +import { Space } from 'antd'; +import styled from 'styled-components'; + +export const EditorContainer = styled.div` + margin-top: 2rem; +`; + +export const FooterContainer = styled(Space)` + display: flex; +`; diff --git a/frontend/src/container/ListOfDashboard/SearchFilter/Dashboard.machine.tsx b/frontend/src/container/ListOfDashboard/SearchFilter/Dashboard.machine.tsx new file mode 100644 index 0000000000..daeee9f023 --- /dev/null +++ b/frontend/src/container/ListOfDashboard/SearchFilter/Dashboard.machine.tsx @@ -0,0 +1,50 @@ +import { createMachine } from 'xstate'; + +export const DashboardSearchAndFilter = createMachine({ + tsTypes: {} as import('./Dashboard.machine.typegen').Typegen0, + initial: 'Idle', + states: { + Category: { + on: { + NEXT: { + actions: 'onSelectOperator', + target: 'Operator', + }, + onBlur: { + actions: 'onBlurPurge', + target: 'Idle', + }, + }, + }, + Operator: { + on: { + NEXT: { + actions: 'onSelectValue', + target: 'Value', + }, + onBlur: { + actions: 'onBlurPurge', + target: 'Idle', + }, + }, + }, + Value: { + on: { + onBlur: { + actions: ['onValidateQuery', 'onBlurPurge'], + target: 'Idle', + }, + }, + }, + Idle: { + on: { + NEXT: { + actions: 'onSelectCategory', + description: 'Select Category', + target: 'Category', + }, + }, + }, + }, + id: 'Dashboard Search And Filter', +}); diff --git a/frontend/src/container/ListOfDashboard/SearchFilter/Dashboard.machine.typegen.ts b/frontend/src/container/ListOfDashboard/SearchFilter/Dashboard.machine.typegen.ts new file mode 100644 index 0000000000..50fdc449c3 --- /dev/null +++ b/frontend/src/container/ListOfDashboard/SearchFilter/Dashboard.machine.typegen.ts @@ -0,0 +1,32 @@ +// This file was automatically generated. Edits will be overwritten + +export interface Typegen0 { + '@@xstate/typegen': true; + eventsCausingActions: { + onSelectOperator: 'NEXT'; + onBlurPurge: 'onBlur'; + onSelectValue: 'NEXT'; + onValidateQuery: 'onBlur'; + onSelectCategory: 'NEXT'; + }; + internalEvents: { + 'xstate.init': { type: 'xstate.init' }; + }; + invokeSrcNameMap: {}; + missingImplementations: { + actions: + | 'onSelectOperator' + | 'onBlurPurge' + | 'onSelectValue' + | 'onValidateQuery' + | 'onSelectCategory'; + services: never; + guards: never; + delays: never; + }; + eventsCausingServices: {}; + eventsCausingGuards: {}; + eventsCausingDelays: {}; + matchesStates: 'Category' | 'Operator' | 'Value' | 'Idle'; + tags: never; +} diff --git a/frontend/src/container/ListOfDashboard/SearchFilter/QueryChip.tsx b/frontend/src/container/ListOfDashboard/SearchFilter/QueryChip.tsx new file mode 100644 index 0000000000..04825b7a81 --- /dev/null +++ b/frontend/src/container/ListOfDashboard/SearchFilter/QueryChip.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import { QueryChipContainer, QueryChipItem } from './styles'; +import { IQueryStructure } from './types'; + +export default function QueryChip({ + queryData, + onRemove, +}: { + queryData: IQueryStructure; + onRemove: (id: string) => void; +}): JSX.Element { + const { category, operator, value, id } = queryData; + return ( + + {category} + {operator} + onRemove(id)}> + {Array.isArray(value) ? value.join(', ') : null} + + + ); +} diff --git a/frontend/src/container/ListOfDashboard/SearchFilter/index.tsx b/frontend/src/container/ListOfDashboard/SearchFilter/index.tsx new file mode 100644 index 0000000000..144bbb5b4b --- /dev/null +++ b/frontend/src/container/ListOfDashboard/SearchFilter/index.tsx @@ -0,0 +1,217 @@ +import { CloseCircleFilled } from '@ant-design/icons'; +import { useMachine } from '@xstate/react'; +import { Button, Select } from 'antd'; +import { RefSelectProps } from 'antd/lib/select'; +import history from 'lib/history'; +import { filter, map } from 'lodash-es'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import { Dashboard } from 'types/api/dashboard/getAll'; +import AppReducer from 'types/reducer/app'; +import { v4 as uuidv4 } from 'uuid'; + +import { DashboardSearchAndFilter } from './Dashboard.machine'; +import QueryChip from './QueryChip'; +import { QueryChipItem, SearchContainer } from './styles'; +import { IOptionsData, IQueryStructure, TCategory, TOperator } from './types'; +import { + convertQueriesToURLQuery, + convertURLQueryStringToQuery, + executeSearchQueries, + OptionsSchemas, + OptionsValueResolution, +} from './utils'; + +function SearchFilter({ + searchData, + filterDashboards, +}: { + searchData: Dashboard[]; + filterDashboards: (filteredDashboards: Dashboard[]) => void; +}): JSX.Element { + const { isDarkMode } = useSelector((state) => state.app); + const [category, setCategory] = useState(); + const [optionsData, setOptionsData] = useState( + OptionsSchemas.attribute, + ); + const selectRef = useRef() as React.MutableRefObject; + const [selectedValues, setSelectedValues] = useState([]); + const [staging, setStaging] = useState([]); + const [queries, setQueries] = useState([]); + + useEffect(() => { + const searchQueryString = new URLSearchParams(history.location.search).get( + 'search', + ); + if (searchQueryString) + setQueries(convertURLQueryStringToQuery(searchQueryString) || []); + }, []); + useEffect(() => { + filterDashboards(executeSearchQueries(queries, searchData)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [queries, searchData]); + + const updateURLWithQuery = useCallback( + (inputQueries?: IQueryStructure[]): void => { + history.push({ + pathname: history.location.pathname, + search: + inputQueries || queries + ? `?search=${convertQueriesToURLQuery(inputQueries || queries)}` + : '', + }); + }, + [queries], + ); + + useEffect(() => { + if (Array.isArray(queries) && queries.length > 0) { + updateURLWithQuery(); + } + }, [queries, updateURLWithQuery]); + + const [state, send] = useMachine(DashboardSearchAndFilter, { + actions: { + onSelectCategory: () => { + setOptionsData(OptionsSchemas.attribute); + }, + onSelectOperator: () => { + setOptionsData(OptionsSchemas.operator); + }, + onSelectValue: () => { + setOptionsData( + OptionsValueResolution(category as TCategory, searchData) as IOptionsData, + ); + }, + onBlurPurge: () => { + setSelectedValues([]); + setStaging([]); + }, + onValidateQuery: () => { + if (staging.length <= 2 && selectedValues.length === 0) { + return; + } + setQueries([ + ...queries, + { + id: uuidv4(), + category: staging[0] as string, + operator: staging[1] as TOperator, + value: selectedValues, + }, + ]); + }, + }, + }); + + const nextState = (): void => { + send('NEXT'); + }; + + const removeQueryById = (queryId: string): void => { + setQueries((queries) => { + const updatedQueries = filter(queries, ({ id }) => id !== queryId); + updateURLWithQuery(updatedQueries); + return updatedQueries; + }); + }; + + const handleChange = (value: never | string[]): void => { + if (!value) { + return; + } + if (optionsData.mode) { + setSelectedValues(value.filter(Boolean)); + return; + } + setStaging([...staging, value]); + + if (state.value === 'Category') { + setCategory(`${value}`.toLowerCase() as TCategory); + } + nextState(); + setSelectedValues([]); + }; + const handleFocus = (): void => { + if (state.value === 'Idle') { + send('NEXT'); + selectRef.current?.focus(); + } + }; + + const handleBlur = (): void => { + send('onBlur'); + selectRef?.current?.blur(); + }; + + const clearQueries = (): void => { + setQueries([]); + history.push({ + pathname: history.location.pathname, + search: ``, + }); + }; + + return ( + + + {map(queries, (query) => ( + + ))} + {map(staging, (value) => ( + + {value as string} + + ))} + + {optionsData && ( + + {optionsData.options && + Array.isArray(optionsData.options) && + optionsData.options.map( + (optionItem): JSX.Element => { + return ( + + {optionItem.name} + + ); + }, + )} + + )} + {queries && queries.length > 0 && ( + } type="text" onClick={clearQueries} /> + )} + + ); +} + +export default SearchFilter; diff --git a/frontend/src/container/ListOfDashboard/SearchFilter/styles.ts b/frontend/src/container/ListOfDashboard/SearchFilter/styles.ts new file mode 100644 index 0000000000..dc03219e1e --- /dev/null +++ b/frontend/src/container/ListOfDashboard/SearchFilter/styles.ts @@ -0,0 +1,30 @@ +import { grey } from '@ant-design/colors'; +import { Tag } from 'antd'; +import styled from 'styled-components'; + +export const SearchContainer = styled.div<{ + isDarkMode: boolean; +}>` + background: ${({ isDarkMode }): string => (isDarkMode ? '#000' : '#fff')}; + width: 100%; + display: flex; + align-items: center; + gap: 0.2rem; + padding: 0.2rem 0; + margin: 1rem 0; + border: 1px solid #ccc5; +`; +export const QueryChipContainer = styled.span` + display: flex; + align-items: center; + margin-right: 0.5rem; + &:hover { + & > * { + background: ${grey.primary}44; + } + } +`; + +export const QueryChipItem = styled(Tag)` + margin-right: 0.1rem; +`; diff --git a/frontend/src/container/ListOfDashboard/SearchFilter/types.ts b/frontend/src/container/ListOfDashboard/SearchFilter/types.ts new file mode 100644 index 0000000000..a9e8a7e8b9 --- /dev/null +++ b/frontend/src/container/ListOfDashboard/SearchFilter/types.ts @@ -0,0 +1,18 @@ +export type TOperator = '=' | '!='; + +export type TCategory = 'title' | 'description' | 'tags'; +export interface IQueryStructure { + category: string; + id: string; + operator: TOperator; + value: string | string[]; +} + +interface IOptions { + name: string; + value?: string; +} +export interface IOptionsData { + mode: undefined | 'tags' | 'multiple'; + options: IOptions[] | []; +} diff --git a/frontend/src/container/ListOfDashboard/SearchFilter/utils.ts b/frontend/src/container/ListOfDashboard/SearchFilter/utils.ts new file mode 100644 index 0000000000..5f9b37cc3e --- /dev/null +++ b/frontend/src/container/ListOfDashboard/SearchFilter/utils.ts @@ -0,0 +1,152 @@ +/* eslint-disable no-param-reassign */ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable sonarjs/cognitive-complexity */ +import { decode, encode } from 'js-base64'; +import { flattenDeep, map, uniqWith } from 'lodash-es'; +import { Dashboard } from 'types/api/dashboard/getAll'; + +import { IOptionsData, IQueryStructure, TCategory, TOperator } from './types'; + +export const convertQueriesToURLQuery = ( + queries: IQueryStructure[], +): string => { + if (!queries || !queries.length) { + return ''; + } + + return encode(JSON.stringify(queries)); +}; + +export const convertURLQueryStringToQuery = ( + queryString: string, +): IQueryStructure[] => { + return JSON.parse(decode(queryString)); +}; + +export const resolveOperator = ( + result: unknown, + operator: TOperator, +): boolean => { + if (operator === '!=') { + return !result; + } + if (operator === '=') { + return !!result; + } + return !!result; +}; +export const executeSearchQueries = ( + queries: IQueryStructure[] = [], + searchData: Dashboard[] = [], +): Dashboard[] => { + if (!searchData.length || !queries.length) { + return searchData; + } + + queries.forEach((query: IQueryStructure) => { + const { operator } = query; + let { value } = query; + const categoryLowercase: TCategory = `${query.category}`.toLowerCase() as + | 'title' + | 'description'; + value = flattenDeep([value]); + + searchData = searchData.filter(({ data: searchPayload }: Dashboard) => { + try { + const searchSpace = + flattenDeep([searchPayload[categoryLowercase]]).filter(Boolean) || null; + if (!searchSpace || !searchSpace.length) + return resolveOperator(false, operator); + + for (const searchSpaceItem of searchSpace) { + if (searchSpaceItem) + for (const queryValue of value) { + if (searchSpaceItem.match(queryValue)) { + return resolveOperator(true, operator); + } + } + } + } catch (error) { + console.error(error); + } + return resolveOperator(false, operator); + }); + }); + return searchData; +}; + +export const OptionsSchemas = { + attribute: { + mode: undefined, + options: [ + { + name: 'Title', + }, + { + name: 'Description', + }, + { + name: 'Tags', + }, + ], + }, + operator: { + mode: undefined, + options: [ + { + value: '=', + name: 'Equal', + }, + { + name: 'Not Equal', + value: '!=', + }, + ], + }, +}; + +export function OptionsValueResolution( + category: TCategory, + searchData: Dashboard[], +): Record | IOptionsData { + const OptionsValueSchema = { + title: { + mode: 'tags', + options: uniqWith( + map(searchData, (searchItem) => ({ name: searchItem.data.title })), + (prev, next) => prev.name === next.name, + ), + }, + description: { + mode: 'tags', + options: uniqWith( + map(searchData, (searchItem) => + searchItem.data.description + ? { + name: searchItem.data.description, + value: searchItem.data.description, + } + : null, + ).filter(Boolean), + (prev, next) => prev?.name === next?.name, + ), + }, + tags: { + mode: 'tags', + options: uniqWith( + map( + flattenDeep( + map(searchData, (searchItem) => searchItem.data.tags).filter(Boolean), + ), + (tag) => ({ name: tag }), + ), + (prev, next) => prev.name === next.name, + ), + }, + }; + + return ( + OptionsValueSchema[category] || + ({ mode: undefined, options: [] } as IOptionsData) + ); +} diff --git a/frontend/src/container/ListOfDashboard/dashboardSearchAndFilter.ts b/frontend/src/container/ListOfDashboard/dashboardSearchAndFilter.ts new file mode 100644 index 0000000000..9e10994c4e --- /dev/null +++ b/frontend/src/container/ListOfDashboard/dashboardSearchAndFilter.ts @@ -0,0 +1,24 @@ +import { Dashboard } from 'types/api/dashboard/getAll'; + +interface IDashboardSearchData { + title: string; + description: string | undefined; + tags: string[]; + id: string; +} + +export const generateSearchData = ( + dashboards: Dashboard[], +): IDashboardSearchData[] => { + const dashboardSearchData: IDashboardSearchData[] = []; + + dashboards.forEach((dashboard) => { + dashboardSearchData.push({ + id: dashboard.uuid, + title: dashboard.data.title, + description: dashboard.data.description, + tags: dashboard.data.tags || [], + }); + }); + return dashboardSearchData; +}; diff --git a/frontend/src/container/ListOfDashboard/index.tsx b/frontend/src/container/ListOfDashboard/index.tsx index 492da5c846..64f3f573c1 100644 --- a/frontend/src/container/ListOfDashboard/index.tsx +++ b/frontend/src/container/ListOfDashboard/index.tsx @@ -1,18 +1,30 @@ -/* eslint-disable react/no-unstable-nested-components */ import { PlusOutlined } from '@ant-design/icons'; -import { Row, Table, TableColumnProps, Typography } from 'antd'; +import { + Card, + Dropdown, + Menu, + Row, + Table, + TableColumnProps, + Typography, +} from 'antd'; import createDashboard from 'api/dashboard/create'; import { AxiosError } from 'axios'; import TextToolTip from 'components/TextToolTip'; import ROUTES from 'constants/routes'; +import SearchFilter from 'container/ListOfDashboard/SearchFilter'; +import useComponentPermission from 'hooks/useComponentPermission'; import history from 'lib/history'; -import React, { useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import { generatePath } from 'react-router-dom'; import { AppState } from 'store/reducers'; +import { Dashboard } from 'types/api/dashboard/getAll'; +import AppReducer from 'types/reducer/app'; import DashboardReducer from 'types/reducer/dashboards'; -import { v4 } from 'uuid'; +import ImportJSON from './ImportJSON'; import { ButtonContainer, NewDashboardButton, TableContainer } from './styles'; import Createdby from './TableComponents/CreatedBy'; import DateComponent from './TableComponents/Date'; @@ -24,7 +36,24 @@ function ListOfAllDashboard(): JSX.Element { const { dashboards, loading } = useSelector( (state) => state.dashboards, ); + const { role } = useSelector((state) => state.app); + const [action, createNewDashboard, newDashboard] = useComponentPermission( + ['action', 'create_new_dashboards', 'new_dashboard'], + role, + ); + + const { t } = useTranslation('dashboard'); + const [ + isImportJSONModalVisible, + setIsImportJSONModalVisible, + ] = useState(false); + + const [filteredDashboards, setFilteredDashboards] = useState(); + + useEffect(() => { + setFilteredDashboards(dashboards); + }, [dashboards]); const [newDashboardState, setNewDashboardState] = useState({ loading: false, error: false, @@ -68,15 +97,18 @@ function ListOfAllDashboard(): JSX.Element { }, render: DateComponent, }, - { + ]; + + if (action) { + columns.push({ title: 'Action', dataIndex: '', key: 'x', render: DeleteButton, - }, - ]; + }); + } - const data: Data[] = dashboards.map((e) => ({ + const data: Data[] = (filteredDashboards || dashboards).map((e) => ({ createdBy: e.created_at, description: e.data.description || '', id: e.uuid, @@ -88,24 +120,20 @@ function ListOfAllDashboard(): JSX.Element { const onNewDashboardHandler = useCallback(async () => { try { - const newDashboardId = v4(); setNewDashboardState({ ...newDashboardState, loading: true, }); const response = await createDashboard({ - uuid: newDashboardId, - title: 'Sample Title', + title: t('new_dashboard_title', { + ns: 'dashboard', + }), }); if (response.statusCode === 200) { - setNewDashboardState({ - ...newDashboardState, - loading: false, - }); history.push( generatePath(ROUTES.DASHBOARD, { - dashboardId: newDashboardId, + dashboardId: response.payload.uuid, }), ); } else { @@ -123,9 +151,9 @@ function ListOfAllDashboard(): JSX.Element { errorMessage: (error as AxiosError).toString() || 'Something went Wrong', }); } - }, [newDashboardState]); + }, [newDashboardState, t]); - const getText = (): string => { + const getText = useCallback(() => { if (!newDashboardState.error && !newDashboardState.loading) { return 'New Dashboard'; } @@ -135,50 +163,103 @@ function ListOfAllDashboard(): JSX.Element { } return newDashboardState.errorMessage; + }, [ + newDashboardState.error, + newDashboardState.errorMessage, + newDashboardState.loading, + ]); + + const onModalHandler = (): void => { + setIsImportJSONModalVisible((state) => !state); }; + const menu = useMemo( + () => ( + + {createNewDashboard && ( + + {t('create_dashboard')} + + )} + + {t('import_json')} + + + ), + [createNewDashboard, loading, onNewDashboardHandler, t], + ); + + const GetHeader = useMemo( + () => ( + + Dashboard List + + + + {newDashboard && ( + + } + type="primary" + loading={newDashboardState.loading} + danger={newDashboardState.error} + > + {getText()} + + + )} + + + ), + [ + getText, + menu, + newDashboard, + newDashboardState.error, + newDashboardState.loading, + ], + ); + return ( - - { - return ( - - Dashboard List + + {GetHeader} - - + {!loading && ( + + )} - } - type="primary" - loading={newDashboardState.loading} - danger={newDashboardState.error} - > - {getText()} - - - - ); - }} - columns={columns} - dataSource={data} - showSorterTooltip - /> - + + + + + ); } diff --git a/frontend/src/container/Login/index.tsx b/frontend/src/container/Login/index.tsx new file mode 100644 index 0000000000..0705a5d2e9 --- /dev/null +++ b/frontend/src/container/Login/index.tsx @@ -0,0 +1,120 @@ +import { Button, Input, notification, Space, Typography } from 'antd'; +import loginApi from 'api/user/login'; +import afterLogin from 'AppRoutes/utils'; +import ROUTES from 'constants/routes'; +import history from 'lib/history'; +import React, { useState } from 'react'; + +import { FormContainer, FormWrapper, Label, ParentContainer } from './styles'; + +const { Title } = Typography; + +function Login(): JSX.Element { + const [isLoading, setIsLoading] = useState(false); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + + const onChangeHandler = ( + setFunc: React.Dispatch>, + value: string, + ): void => { + setFunc(value); + }; + + const onSubmitHandler: React.FormEventHandler = async ( + event, + ) => { + try { + event.preventDefault(); + event.persist(); + setIsLoading(true); + + const response = await loginApi({ + email, + password, + }); + if (response.statusCode === 200) { + await afterLogin( + response.payload.userId, + response.payload.accessJwt, + response.payload.refreshJwt, + ); + history.push(ROUTES.APPLICATION); + } else { + notification.error({ + message: response.error || 'Something went wrong', + }); + } + setIsLoading(false); + } catch (error) { + setIsLoading(false); + notification.error({ + message: 'Something went wrong', + }); + } + }; + + return ( + + + Login to SigNoz + + Email + onChangeHandler(setEmail, event.target.value)} + value={email} + disabled={isLoading} + /> + + + Password + + onChangeHandler(setPassword, event.target.value) + } + disabled={isLoading} + value={password} + /> + + + + Login + + { + history.push(ROUTES.SIGN_UP); + }} + style={{ fontWeight: 700 }} + > + Create an account + + + + If you have forgotten you password, ask your admin to reset password and + send you a new invite link + + + + + ); +} + +export default Login; diff --git a/frontend/src/container/Login/styles.ts b/frontend/src/container/Login/styles.ts new file mode 100644 index 0000000000..8a635b487d --- /dev/null +++ b/frontend/src/container/Login/styles.ts @@ -0,0 +1,28 @@ +import { Card } from 'antd'; +import styled from 'styled-components'; + +export const FormWrapper = styled(Card)` + display: flex; + justify-content: center; + max-width: 432px; + flex: 1; + align-items: flex-start; +`; + +export const Label = styled.label` + margin-bottom: 11px; + margin-top: 19px; + display: inline-block; + font-size: 1rem; + line-height: 24px; +`; + +export const FormContainer = styled.form` + display: flex; + flex-direction: column; + align-items: flex-start; +`; + +export const ParentContainer = styled.div` + width: 100%; +`; diff --git a/frontend/src/container/MetricsApplication/ResourceAttributesFilter/QueryChip.tsx b/frontend/src/container/MetricsApplication/ResourceAttributesFilter/QueryChip.tsx new file mode 100644 index 0000000000..09c5d27471 --- /dev/null +++ b/frontend/src/container/MetricsApplication/ResourceAttributesFilter/QueryChip.tsx @@ -0,0 +1,32 @@ +import { convertMetricKeyToTrace } from 'lib/resourceAttributes'; +import React from 'react'; + +import { QueryChipContainer, QueryChipItem } from './styles'; +import { IResourceAttributeQuery } from './types'; + +interface IQueryChipProps { + queryData: IResourceAttributeQuery; + onClose: (id: string) => void; + disabled: boolean; +} + +export default function QueryChip({ + queryData, + onClose, + disabled, +}: IQueryChipProps): JSX.Element { + return ( + + {convertMetricKeyToTrace(queryData.tagKey)} + {queryData.operator} + { + if (!disabled) onClose(queryData.id); + }} + > + {queryData.tagValue.join(', ')} + + + ); +} diff --git a/frontend/src/container/MetricsApplication/ResourceAttributesFilter/ResourceAttributesFilter.Machine.ts b/frontend/src/container/MetricsApplication/ResourceAttributesFilter/ResourceAttributesFilter.Machine.ts new file mode 100644 index 0000000000..3b9078f76b --- /dev/null +++ b/frontend/src/container/MetricsApplication/ResourceAttributesFilter/ResourceAttributesFilter.Machine.ts @@ -0,0 +1,61 @@ +import { createMachine } from 'xstate'; + +export const ResourceAttributesFilterMachine = + /** @xstate-layout N4IgpgJg5mDOIC5QBECGsAWAjA9qgThAAQDKYBAxhkQIIB2xAYgJYA2ALmPgHQAqqUANJgAngGIAcgFEAGr0SgADjljN2zHHQUgAHogAcAFgAM3AOz6ATAEYAzJdsA2Y4cOWAnABoQIxAFpDR2tuQ319AFYTcKdbFycAX3jvNExcAmIySmp6JjZOHn4hUTFNACFWAFd8bWVVdU1tPQQzY1MXY2tDdzNHM3dHd0NvXwR7biMTa313S0i+63DE5PRsPEJScnwqWgYiFg4uPgFhcQAlKRIpeSQQWrUNLRumx3Czbg8TR0sbS31jfUcw38fW47gBHmm4XCVms3SWIBSq3SGyyO1yBx4AHlFFxUOwcPhJLJrkoVPcGk9ENYFuF3i5YR0wtEHECEAEgiEmV8zH1DLYzHZ4Yi0utMltsrt9vluNjcfjCWVKtUbnd6o9QE1rMYBtxbGFvsZ3NrZj1WdYOfotUZLX0XEFHEKViKMpttjk9nlDrL8HiCWJzpcSbcyWrGoh3NCQj0zK53P1ph1WeFLLqnJZ2s5vmZLA6kginWsXaj3VLDoUAGqoSpgEp0cpVGohh5hhDWDy0sz8zruakzamWVm-Qyg362V5-AZOayO1KFlHitEejFHKCV6v+i5XRt1ZuU1s52zjNOOaZfdOWIY+RDZ0Hc6ZmKEXqyLPPCudit2Sz08ACSEFYNbSHI27kuquiIOEjiONwjJgrM3RWJYZisgEIJgnYPTmuEdi2OaiR5nQOAQHA2hvsiH4Sui0qFCcIGhnuLSmP0YJuJ2xjJsmKELG8XZTK0tjdHG06vgW5GupRS7St6vrKqSO4UhqVL8TBWp8o4eqdl0A5Xmy3G6gK56-B4uERDOSKiuJi6lgUAhrhUYB0buimtrEKZBDYrxaS0OZca8+ltheybOI4hivGZzrzp+VGHH+AGOQp4EIHy+ghNYnawtG4TsbYvk8QKfHGAJfQ9uF76WSW37xWBTSGJ0qXpd0vRZdEKGPqC2YeO2-zfO4+HxEAA */ + createMachine({ + tsTypes: {} as import('./ResourceAttributesFilter.Machine.typegen').Typegen0, + initial: 'Idle', + states: { + TagKey: { + on: { + NEXT: { + actions: 'onSelectOperator', + target: 'Operator', + }, + onBlur: { + actions: 'onBlurPurge', + target: 'Idle', + }, + RESET: { + target: 'Idle', + }, + }, + }, + Operator: { + on: { + NEXT: { + actions: 'onSelectTagValue', + target: 'TagValue', + }, + onBlur: { + actions: 'onBlurPurge', + target: 'Idle', + }, + RESET: { + target: 'Idle', + }, + }, + }, + TagValue: { + on: { + onBlur: { + actions: ['onValidateQuery', 'onBlurPurge'], + target: 'Idle', + }, + RESET: { + target: 'Idle', + }, + }, + }, + Idle: { + on: { + NEXT: { + actions: 'onSelectTagKey', + description: 'Select Category', + target: 'TagKey', + }, + }, + }, + }, + id: 'Dashboard Search And Filter', + }); diff --git a/frontend/src/container/MetricsApplication/ResourceAttributesFilter/ResourceAttributesFilter.Machine.typegen.ts b/frontend/src/container/MetricsApplication/ResourceAttributesFilter/ResourceAttributesFilter.Machine.typegen.ts new file mode 100644 index 0000000000..e7f7ee3de7 --- /dev/null +++ b/frontend/src/container/MetricsApplication/ResourceAttributesFilter/ResourceAttributesFilter.Machine.typegen.ts @@ -0,0 +1,32 @@ +// This file was automatically generated. Edits will be overwritten + +export interface Typegen0 { + '@@xstate/typegen': true; + eventsCausingActions: { + onSelectOperator: 'NEXT'; + onBlurPurge: 'onBlur'; + onSelectTagValue: 'NEXT'; + onValidateQuery: 'onBlur'; + onSelectTagKey: 'NEXT'; + }; + internalEvents: { + 'xstate.init': { type: 'xstate.init' }; + }; + invokeSrcNameMap: {}; + missingImplementations: { + actions: + | 'onSelectOperator' + | 'onBlurPurge' + | 'onSelectTagValue' + | 'onValidateQuery' + | 'onSelectTagKey'; + services: never; + guards: never; + delays: never; + }; + eventsCausingServices: {}; + eventsCausingGuards: {}; + eventsCausingDelays: {}; + matchesStates: 'TagKey' | 'Operator' | 'TagValue' | 'Idle'; + tags: never; +} diff --git a/frontend/src/container/MetricsApplication/ResourceAttributesFilter/index.tsx b/frontend/src/container/MetricsApplication/ResourceAttributesFilter/index.tsx new file mode 100644 index 0000000000..a531e3d674 --- /dev/null +++ b/frontend/src/container/MetricsApplication/ResourceAttributesFilter/index.tsx @@ -0,0 +1,225 @@ +import { CloseCircleFilled } from '@ant-design/icons'; +import { useMachine } from '@xstate/react'; +import { Button, Select, Spin } from 'antd'; +import ROUTES from 'constants/routes'; +import history from 'lib/history'; +import { convertMetricKeyToTrace } from 'lib/resourceAttributes'; +import { map } from 'lodash-es'; +import React, { useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { ResetInitialData } from 'store/actions/metrics/resetInitialData'; +import { SetResourceAttributeQueries } from 'store/actions/metrics/setResourceAttributeQueries'; +import { AppState } from 'store/reducers'; +import AppReducer from 'types/reducer/app'; +import MetricReducer from 'types/reducer/metrics'; +import { v4 as uuid } from 'uuid'; + +import QueryChip from './QueryChip'; +import { ResourceAttributesFilterMachine } from './ResourceAttributesFilter.Machine'; +import { QueryChipItem, SearchContainer } from './styles'; +import { IOption, IResourceAttributeQuery } from './types'; +import { createQuery, GetTagKeys, GetTagValues, OperatorSchema } from './utils'; + +function ResourceAttributesFilter(): JSX.Element | null { + const dispatch = useDispatch(); + const [disabled, setDisabled] = useState( + !(history.location.pathname === ROUTES.APPLICATION), + ); + + useEffect(() => { + const unListen = history.listen(({ pathname }) => { + setDisabled(!(pathname === ROUTES.APPLICATION)); + }); + return (): void => { + if (!history.location.pathname.startsWith(`${ROUTES.APPLICATION}/`)) { + dispatch(ResetInitialData()); + } + unListen(); + }; + }, [dispatch]); + + const { isDarkMode } = useSelector((state) => state.app); + const { resourceAttributeQueries } = useSelector( + (state) => state.metrics, + ); + const [loading, setLoading] = useState(true); + const [selectedValues, setSelectedValues] = useState([]); + const [staging, setStaging] = useState([]); + const [queries, setQueries] = useState([]); + const [optionsData, setOptionsData] = useState<{ + mode: undefined | 'tags' | 'multiple'; + options: IOption[]; + }>({ + mode: undefined, + options: [], + }); + + const dispatchQueries = (updatedQueries: IResourceAttributeQuery[]): void => { + dispatch(SetResourceAttributeQueries(updatedQueries)); + }; + const handleLoading = (isLoading: boolean): void => { + setLoading(isLoading); + if (isLoading) { + setOptionsData({ mode: undefined, options: [] }); + } + }; + const [state, send] = useMachine(ResourceAttributesFilterMachine, { + actions: { + onSelectTagKey: () => { + handleLoading(true); + GetTagKeys() + .then((tagKeys) => setOptionsData({ options: tagKeys, mode: undefined })) + .finally(() => { + handleLoading(false); + }); + }, + onSelectOperator: () => { + setOptionsData({ options: OperatorSchema, mode: undefined }); + }, + onSelectTagValue: () => { + handleLoading(true); + + GetTagValues(staging[0]) + .then((tagValuesOptions) => + setOptionsData({ options: tagValuesOptions, mode: 'multiple' }), + ) + .finally(() => { + handleLoading(false); + }); + }, + onBlurPurge: () => { + setSelectedValues([]); + setStaging([]); + }, + onValidateQuery: (): void => { + if (staging.length < 2 || selectedValues.length === 0) { + return; + } + + const generatedQuery = createQuery([...staging, selectedValues]); + if (generatedQuery) { + dispatchQueries([...queries, generatedQuery]); + } + }, + }, + }); + + useEffect(() => { + setQueries(resourceAttributeQueries); + }, [resourceAttributeQueries]); + + const handleFocus = (): void => { + if (state.value === 'Idle') { + send('NEXT'); + } + }; + + const handleBlur = (): void => { + send('onBlur'); + }; + const handleChange = (value: never): void => { + if (!optionsData.mode) { + setStaging((prevStaging) => [...prevStaging, value]); + setSelectedValues([]); + send('NEXT'); + return; + } + + setSelectedValues([...value]); + }; + + const handleClose = (id: string): void => { + dispatchQueries(queries.filter((queryData) => queryData.id !== id)); + }; + + const handleClearAll = (): void => { + send('RESET'); + dispatchQueries([]); + setStaging([]); + setSelectedValues([]); + }; + const disabledAndEmpty = !!( + !queries.length && + !staging.length && + !selectedValues.length && + disabled + ); + const disabledOrEmpty = !!( + queries.length || + staging.length || + selectedValues.length || + disabled + ); + + if (disabledAndEmpty) { + return null; + } + + return ( + + + {map( + queries, + (query): JSX.Element => { + return ( + + ); + }, + )} + {map(staging, (item, idx) => { + return ( + + {idx === 0 ? convertMetricKeyToTrace(item) : item} + + ); + })} + + {!disabled && ( + + Loading...{' '} + + ) : ( + + No resource attributes available to filter. Please refer docs to send + attributes. + + ) + } + /> + )} + + {(queries.length || staging.length || selectedValues.length) && !disabled ? ( + } type="text" /> + ) : null} + + ); +} + +export default ResourceAttributesFilter; diff --git a/frontend/src/container/MetricsApplication/ResourceAttributesFilter/styles.ts b/frontend/src/container/MetricsApplication/ResourceAttributesFilter/styles.ts new file mode 100644 index 0000000000..94147842f7 --- /dev/null +++ b/frontend/src/container/MetricsApplication/ResourceAttributesFilter/styles.ts @@ -0,0 +1,32 @@ +import { grey } from '@ant-design/colors'; +import { Tag } from 'antd'; +import styled from 'styled-components'; + +export const SearchContainer = styled.div<{ + isDarkMode: boolean; + disabled: boolean; +}>` + background: ${({ isDarkMode }): string => (isDarkMode ? '#000' : '#fff')}; + width: 100%; + display: flex; + align-items: center; + gap: 0.2rem; + padding: 0.2rem; + margin: 1rem 0; + border: 1px solid #ccc5; + ${({ disabled }): string => (disabled ? `cursor: not-allowed;` : '')} +`; +export const QueryChipContainer = styled.span` + display: flex; + align-items: center; + margin-right: 0.5rem; + &:hover { + & > * { + background: ${grey.primary}44; + } + } +`; + +export const QueryChipItem = styled(Tag)` + margin-right: 0.1rem; +`; diff --git a/frontend/src/container/MetricsApplication/ResourceAttributesFilter/types.ts b/frontend/src/container/MetricsApplication/ResourceAttributesFilter/types.ts new file mode 100644 index 0000000000..928b486707 --- /dev/null +++ b/frontend/src/container/MetricsApplication/ResourceAttributesFilter/types.ts @@ -0,0 +1,11 @@ +export interface IOption { + label: string; + value: string; +} + +export interface IResourceAttributeQuery { + id: string; + tagKey: string; + operator: string; + tagValue: string[]; +} diff --git a/frontend/src/container/MetricsApplication/ResourceAttributesFilter/utils.ts b/frontend/src/container/MetricsApplication/ResourceAttributesFilter/utils.ts new file mode 100644 index 0000000000..e8dd7bb503 --- /dev/null +++ b/frontend/src/container/MetricsApplication/ResourceAttributesFilter/utils.ts @@ -0,0 +1,58 @@ +import { + getResourceAttributesTagKeys, + getResourceAttributesTagValues, +} from 'api/metrics/getResourceAttributes'; +import { OperatorConversions } from 'constants/resourceAttributes'; +import { convertMetricKeyToTrace } from 'lib/resourceAttributes'; +import { v4 as uuid } from 'uuid'; + +import { IOption, IResourceAttributeQuery } from './types'; + +export const OperatorSchema: IOption[] = OperatorConversions.map( + (operator) => ({ + label: operator.label, + value: operator.label, + }), +); + +export const GetTagKeys = async (): Promise => { + // if (TagKeysCache) { + // return new Promise((resolve) => { + // resolve(TagKeysCache); + // }); + // } + const { payload } = await getResourceAttributesTagKeys(); + if (!payload || !payload?.data) { + return []; + } + return payload.data.map((tagKey: string) => ({ + label: convertMetricKeyToTrace(tagKey), + value: tagKey, + })); +}; + +export const GetTagValues = async (tagKey: string): Promise => { + const { payload } = await getResourceAttributesTagValues(tagKey); + + if (!payload || !payload?.data) { + return []; + } + return payload.data.filter(Boolean).map((tagValue: string) => ({ + label: tagValue, + value: tagValue, + })); +}; + +export const createQuery = ( + selectedItems: Array = [], +): IResourceAttributeQuery | null => { + if (selectedItems.length === 3) { + return { + id: uuid().slice(0, 8), + tagKey: selectedItems[0] as string, + operator: selectedItems[1] as string, + tagValue: selectedItems[2] as string[], + }; + } + return null; +}; diff --git a/frontend/src/container/MetricsApplication/Tabs/Application.tsx b/frontend/src/container/MetricsApplication/Tabs/Application.tsx index 8b8ae4f38f..b7c44efac4 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Application.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Application.tsx @@ -6,6 +6,7 @@ import FullView from 'container/GridGraphLayout/Graph/FullView'; import convertToNanoSecondsToSecond from 'lib/convertToNanoSecondsToSecond'; import { colors } from 'lib/getRandomColor'; import history from 'lib/history'; +import { convertRawQueriesToTraceSelectedTags } from 'lib/resourceAttributes'; import React, { useRef } from 'react'; import { useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; @@ -21,8 +22,15 @@ function Application({ getWidget }: DashboardProps): JSX.Element { const { servicename } = useParams<{ servicename?: string }>(); const selectedTimeStamp = useRef(0); - const { topEndPoints, serviceOverview } = useSelector( - (state) => state.metrics, + const { + topEndPoints, + serviceOverview, + resourceAttributePromQLQuery, + resourceAttributeQueries, + } = useSelector((state) => state.metrics); + + const selectedTraceTags: string = JSON.stringify( + convertRawQueriesToTraceSelectedTags(resourceAttributeQueries, 'array') || [], ); const onTracePopupClick = (timestamp: number): void => { @@ -36,7 +44,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element { history.replace( `${ ROUTES.TRACE - }?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=[]&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`, + }?${urlParams.toString()}&selected={"serviceName":["${servicename}"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`, ); }; @@ -88,7 +96,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element { history.replace( `${ ROUTES.TRACE - }?${urlParams.toString()}?selected={"serviceName":["${servicename}"],"status":["error"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=[]&isFilterExclude={"serviceName":false,"status":false}&userSelectedFilter={"serviceName":["${servicename}"],"status":["error"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`, + }?${urlParams.toString()}?selected={"serviceName":["${servicename}"],"status":["error"]}&filterToFetchData=["duration","status","serviceName"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&isFilterExclude={"serviceName":false,"status":false}&userSelectedFilter={"serviceName":["${servicename}"],"status":["error"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`, ); }; @@ -185,7 +193,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element { }} widget={getWidget([ { - query: `sum(rate(signoz_latency_count{service_name="${servicename}", span_kind="SPAN_KIND_SERVER"}[2m]))`, + query: `sum(rate(signoz_latency_count{service_name="${servicename}", span_kind="SPAN_KIND_SERVER"${resourceAttributePromQLQuery}}[2m]))`, legend: 'Requests', }, ])} @@ -219,7 +227,7 @@ function Application({ getWidget }: DashboardProps): JSX.Element { }} widget={getWidget([ { - query: `max(sum(rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER", status_code="STATUS_CODE_ERROR"}[1m]) OR rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER", http_status_code=~"5.."}[1m]))*100/sum(rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER"}[1m]))) < 1000 OR vector(0)`, + query: `max(sum(rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER", status_code="STATUS_CODE_ERROR"${resourceAttributePromQLQuery}}[1m]) OR rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER", http_status_code=~"5.."${resourceAttributePromQLQuery}}[1m]))*100/sum(rate(signoz_calls_total{service_name="${servicename}", span_kind="SPAN_KIND_SERVER"${resourceAttributePromQLQuery}}[1m]))) < 1000 OR vector(0)`, legend: 'Error Percentage', }, ])} diff --git a/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx b/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx index 20206c21d7..ba0af031cd 100644 --- a/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx @@ -1,14 +1,19 @@ import { Col } from 'antd'; import FullView from 'container/GridGraphLayout/Graph/FullView'; import React from 'react'; +import { useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; +import { AppState } from 'store/reducers'; import { Widgets } from 'types/api/dashboard/getAll'; +import MetricReducer from 'types/reducer/metrics'; import { Card, GraphContainer, GraphTitle, Row } from '../styles'; function DBCall({ getWidget }: DBCallProps): JSX.Element { const { servicename } = useParams<{ servicename?: string }>(); - + const { resourceAttributePromQLQuery } = useSelector( + (state) => state.metrics, + ); return ( @@ -20,7 +25,7 @@ function DBCall({ getWidget }: DBCallProps): JSX.Element { fullViewOptions={false} widget={getWidget([ { - query: `sum(rate(signoz_db_latency_count{service_name="${servicename}"}[1m])) by (db_system)`, + query: `sum(rate(signoz_db_latency_count{service_name="${servicename}"${resourceAttributePromQLQuery}}[1m])) by (db_system)`, legend: '{{db_system}}', }, ])} @@ -39,7 +44,7 @@ function DBCall({ getWidget }: DBCallProps): JSX.Element { fullViewOptions={false} widget={getWidget([ { - query: `sum(rate(signoz_db_latency_sum{service_name="${servicename}"}[5m]))/sum(rate(signoz_db_latency_count{service_name="${servicename}"}[5m]))`, + query: `sum(rate(signoz_db_latency_sum{service_name="${servicename}"${resourceAttributePromQLQuery}}[5m]))/sum(rate(signoz_db_latency_count{service_name="${servicename}"${resourceAttributePromQLQuery}}[5m]))`, legend: '', }, ])} diff --git a/frontend/src/container/MetricsApplication/Tabs/External.tsx b/frontend/src/container/MetricsApplication/Tabs/External.tsx index 5184338d53..73eca09600 100644 --- a/frontend/src/container/MetricsApplication/Tabs/External.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/External.tsx @@ -1,14 +1,19 @@ import { Col } from 'antd'; import FullView from 'container/GridGraphLayout/Graph/FullView'; import React from 'react'; +import { useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; +import { AppState } from 'store/reducers'; import { Widgets } from 'types/api/dashboard/getAll'; +import MetricReducer from 'types/reducer/metrics'; import { Card, GraphContainer, GraphTitle, Row } from '../styles'; function External({ getWidget }: ExternalProps): JSX.Element { const { servicename } = useParams<{ servicename?: string }>(); - + const { resourceAttributePromQLQuery } = useSelector( + (state) => state.metrics, + ); const legend = '{{http_url}}'; return ( @@ -23,7 +28,7 @@ function External({ getWidget }: ExternalProps): JSX.Element { fullViewOptions={false} widget={getWidget([ { - query: `max((sum(rate(signoz_external_call_latency_count{service_name="${servicename}", status_code="STATUS_CODE_ERROR"}[1m]) OR rate(signoz_external_call_latency_count{service_name="${servicename}", http_status_code=~"5.."}[1m]) OR vector(0)) by (http_url))*100/sum(rate(signoz_external_call_latency_count{service_name="${servicename}"}[1m])) by (http_url)) < 1000 OR vector(0)`, + query: `max((sum(rate(signoz_external_call_latency_count{service_name="${servicename}", status_code="STATUS_CODE_ERROR"${resourceAttributePromQLQuery}}[1m]) OR rate(signoz_external_call_latency_count{service_name="${servicename}", http_status_code=~"5.."${resourceAttributePromQLQuery}}[1m]) OR vector(0)) by (http_url))*100/sum(rate(signoz_external_call_latency_count{service_name="${servicename}"${resourceAttributePromQLQuery}}[1m])) by (http_url)) < 1000 OR vector(0)`, legend, }, ])} @@ -42,7 +47,7 @@ function External({ getWidget }: ExternalProps): JSX.Element { fullViewOptions={false} widget={getWidget([ { - query: `sum(rate(signoz_external_call_latency_sum{service_name="${servicename}"}[5m]))/sum(rate(signoz_external_call_latency_count{service_name="${servicename}"}[5m]))`, + query: `sum(rate(signoz_external_call_latency_sum{service_name="${servicename}"${resourceAttributePromQLQuery}}[5m]))/sum(rate(signoz_external_call_latency_count{service_name="${servicename}"${resourceAttributePromQLQuery}}[5m]))`, legend: 'Average Duration', }, ])} @@ -63,7 +68,7 @@ function External({ getWidget }: ExternalProps): JSX.Element { fullViewOptions={false} widget={getWidget([ { - query: `sum(rate(signoz_external_call_latency_count{service_name="${servicename}"}[5m])) by (http_url)`, + query: `sum(rate(signoz_external_call_latency_count{service_name="${servicename}"${resourceAttributePromQLQuery}}[5m])) by (http_url)`, legend, }, ])} @@ -82,7 +87,7 @@ function External({ getWidget }: ExternalProps): JSX.Element { fullViewOptions={false} widget={getWidget([ { - query: `(sum(rate(signoz_external_call_latency_sum{service_name="${servicename}"}[5m])) by (http_url))/(sum(rate(signoz_external_call_latency_count{service_name="${servicename}"}[5m])) by (http_url))`, + query: `(sum(rate(signoz_external_call_latency_sum{service_name="${servicename}"${resourceAttributePromQLQuery}}[5m])) by (http_url))/(sum(rate(signoz_external_call_latency_count{service_name="${servicename}"${resourceAttributePromQLQuery}}[5m])) by (http_url))`, legend, }, ])} diff --git a/frontend/src/container/MetricsApplication/Tabs/styles.ts b/frontend/src/container/MetricsApplication/Tabs/styles.ts index 500e2b9b74..bf89345063 100644 --- a/frontend/src/container/MetricsApplication/Tabs/styles.ts +++ b/frontend/src/container/MetricsApplication/Tabs/styles.ts @@ -11,5 +11,5 @@ export const Button = styled(ButtonComponent)` } `; export const TableContainerCard = styled(Card)` - overflow-x: scroll; + overflow-x: auto; `; diff --git a/frontend/src/container/MetricsApplication/TopEndpointsTable.tsx b/frontend/src/container/MetricsApplication/TopEndpointsTable.tsx index 71f19f3d7a..9eb189e0ba 100644 --- a/frontend/src/container/MetricsApplication/TopEndpointsTable.tsx +++ b/frontend/src/container/MetricsApplication/TopEndpointsTable.tsx @@ -3,16 +3,25 @@ import { ColumnsType } from 'antd/lib/table'; import { METRICS_PAGE_QUERY_PARAM } from 'constants/query'; import ROUTES from 'constants/routes'; import history from 'lib/history'; +import { convertRawQueriesToTraceSelectedTags } from 'lib/resourceAttributes'; import React from 'react'; import { useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; import { AppState } from 'store/reducers'; import { GlobalReducer } from 'types/reducer/globalTime'; +import MetricReducer from 'types/reducer/metrics'; function TopEndpointsTable(props: TopEndpointsTableProps): JSX.Element { const { minTime, maxTime } = useSelector( (state) => state.globalTime, ); + const { resourceAttributeQueries } = useSelector( + (state) => state.metrics, + ); + + const selectedTraceTags: string = JSON.stringify( + convertRawQueriesToTraceSelectedTags(resourceAttributeQueries) || [], + ); const { data } = props; @@ -33,7 +42,7 @@ function TopEndpointsTable(props: TopEndpointsTableProps): JSX.Element { history.push( `${ ROUTES.TRACE - }?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"operation":["${operation}"]}&filterToFetchData=["duration","status","serviceName","operation"]&spanAggregateCurrentPage=1&selectedTags=[]&&isFilterExclude={"serviceName":false,"operation":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"],"operation":["${operation}"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`, + }?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"operation":["${operation}"]}&filterToFetchData=["duration","status","serviceName","operation"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false,"operation":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"],"operation":["${operation}"]}&spanAggregateCurrentPage=1&spanAggregateOrder=ascend`, ); }; diff --git a/frontend/src/container/MetricsApplication/index.tsx b/frontend/src/container/MetricsApplication/index.tsx index ceedaa5018..51725ab80a 100644 --- a/frontend/src/container/MetricsApplication/index.tsx +++ b/frontend/src/container/MetricsApplication/index.tsx @@ -2,6 +2,7 @@ import { Tabs } from 'antd'; import React from 'react'; import { Widgets } from 'types/api/dashboard/getAll'; +import ResourceAttributesFilter from './ResourceAttributesFilter'; import Application from './Tabs/Application'; import DBCall from './Tabs/DBCall'; import External from './Tabs/External'; @@ -31,19 +32,22 @@ function ServiceMetrics(): JSX.Element { }; return ( - - - - + <> + + + + + - - - + + + - - - - + + + + + > ); } diff --git a/frontend/src/container/MetricsTable/index.tsx b/frontend/src/container/MetricsTable/index.tsx index de9fa4976a..75e80a5062 100644 --- a/frontend/src/container/MetricsTable/index.tsx +++ b/frontend/src/container/MetricsTable/index.tsx @@ -5,7 +5,7 @@ import { SKIP_ONBOARDING } from 'constants/onboarding'; import ROUTES from 'constants/routes'; import React, { useState } from 'react'; import { useSelector } from 'react-redux'; -import { Link } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; import { AppState } from 'store/reducers'; import { ServicesList } from 'types/api/metrics/getService'; import MetricReducer from 'types/reducer/metrics'; @@ -14,6 +14,7 @@ import SkipBoardModal from './SkipOnBoardModal'; import { Container, Name } from './styles'; function Metrics(): JSX.Element { + const { search } = useLocation(); const [skipOnboarding, setSkipOnboarding] = useState( localStorageGet(SKIP_ONBOARDING) === 'true', ); @@ -41,9 +42,8 @@ function Metrics(): JSX.Element { title: 'Application', dataIndex: 'serviceName', key: 'serviceName', - // eslint-disable-next-line react/display-name render: (text: string): JSX.Element => ( - + {text} ), diff --git a/frontend/src/container/MySettings/Password/index.tsx b/frontend/src/container/MySettings/Password/index.tsx new file mode 100644 index 0000000000..1efa58f565 --- /dev/null +++ b/frontend/src/container/MySettings/Password/index.tsx @@ -0,0 +1,151 @@ +import { Button, notification, Space, Typography } from 'antd'; +import changeMyPassword from 'api/user/changeMyPassword'; +import { isPasswordNotValidMessage, isPasswordValid } from 'pages/SignUp/utils'; +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import AppReducer from 'types/reducer/app'; + +import { Password } from '../styles'; + +function PasswordContainer(): JSX.Element { + const [currentPassword, setCurrentPassword] = useState(''); + const [updatePassword, setUpdatePassword] = useState(''); + const { t } = useTranslation(['routes', 'settings', 'common']); + const { user } = useSelector((state) => state.app); + const [isLoading, setIsLoading] = useState(false); + const [isPasswordPolicyError, setIsPasswordPolicyError] = useState( + false, + ); + + const defaultPlaceHolder = t('input_password', { + ns: 'settings', + }); + + useEffect(() => { + if (currentPassword && !isPasswordValid(currentPassword)) { + setIsPasswordPolicyError(true); + } else { + setIsPasswordPolicyError(false); + } + }, [currentPassword]); + + if (!user) { + return ; + } + + const onChangePasswordClickHandler = async (): Promise => { + try { + setIsLoading(true); + + if (!isPasswordValid(currentPassword)) { + setIsPasswordPolicyError(true); + setIsLoading(false); + return; + } + + const { statusCode, error } = await changeMyPassword({ + newPassword: updatePassword, + oldPassword: currentPassword, + userId: user.userId, + }); + + if (statusCode === 200) { + notification.success({ + message: t('success', { + ns: 'common', + }), + }); + } else { + notification.error({ + message: + error || + t('something_went_wrong', { + ns: 'common', + }), + }); + } + setIsLoading(false); + } catch (error) { + setIsLoading(false); + + notification.error({ + message: t('something_went_wrong', { + ns: 'common', + }), + }); + } + }; + + return ( + + + {t('change_password', { + ns: 'settings', + })} + + + + {t('current_password', { + ns: 'settings', + })} + + { + setCurrentPassword(event.target.value); + }} + value={currentPassword} + /> + + + + {t('new_password', { + ns: 'settings', + })} + + { + const updatedValue = event.target.value; + setUpdatePassword(updatedValue); + }} + value={updatePassword} + /> + + + {isPasswordPolicyError && ( + + {isPasswordNotValidMessage} + + )} + + + {t('change_password', { + ns: 'settings', + })} + + + ); +} + +export default PasswordContainer; diff --git a/frontend/src/container/MySettings/UpdateName/index.tsx b/frontend/src/container/MySettings/UpdateName/index.tsx new file mode 100644 index 0000000000..0b5fd501c7 --- /dev/null +++ b/frontend/src/container/MySettings/UpdateName/index.tsx @@ -0,0 +1,95 @@ +import { Button, notification, Space, Typography } from 'antd'; +import editUser from 'api/user/editUser'; +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDispatch, useSelector } from 'react-redux'; +import { Dispatch } from 'redux'; +import { AppState } from 'store/reducers'; +import AppActions from 'types/actions'; +import { UPDATE_USER } from 'types/actions/app'; +import AppReducer from 'types/reducer/app'; + +import { NameInput } from '../styles'; + +function UpdateName(): JSX.Element { + const { user, role, org } = useSelector( + (state) => state.app, + ); + const { t } = useTranslation(); + const dispatch = useDispatch>(); + + const [changedName, setChangedName] = useState(user?.name || ''); + const [loading, setLoading] = useState(false); + + if (!user || !org) { + return ; + } + + const onClickUpdateHandler = async (): Promise => { + try { + setLoading(true); + const { statusCode } = await editUser({ + name: changedName, + userId: user.userId, + }); + + if (statusCode === 200) { + notification.success({ + message: t('success', { + ns: 'common', + }), + }); + dispatch({ + type: UPDATE_USER, + payload: { + ...user, + name: changedName, + ROLE: role || 'ADMIN', + orgId: org[0].id, + orgName: org[0].name, + }, + }); + } else { + notification.error({ + message: t('something_went_wrong', { + ns: 'common', + }), + }); + } + setLoading(false); + } catch (error) { + notification.error({ + message: t('something_went_wrong', { + ns: 'common', + }), + }); + } + setLoading(false); + }; + + return ( + + + Name + { + setChangedName(event.target.value); + }} + value={changedName} + disabled={loading} + /> + + Update Name + + + + ); +} + +export default UpdateName; diff --git a/frontend/src/container/MySettings/index.tsx b/frontend/src/container/MySettings/index.tsx new file mode 100644 index 0000000000..7ce9e02e42 --- /dev/null +++ b/frontend/src/container/MySettings/index.tsx @@ -0,0 +1,19 @@ +import { Space, Typography } from 'antd'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import Password from './Password'; +import UpdateName from './UpdateName'; + +function MySettings(): JSX.Element { + const { t } = useTranslation(['routes']); + return ( + + {t('my_settings')} + + + + ); +} + +export default MySettings; diff --git a/frontend/src/container/MySettings/styles.ts b/frontend/src/container/MySettings/styles.ts new file mode 100644 index 0000000000..689f3176d0 --- /dev/null +++ b/frontend/src/container/MySettings/styles.ts @@ -0,0 +1,14 @@ +import { Input } from 'antd'; +import styled from 'styled-components'; + +export const Password = styled(Input.Password)` + &&& { + width: 20rem; + } +`; + +export const NameInput = styled(Input)` + &&& { + width: 20rem; + } +`; diff --git a/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx b/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx index bbe352b29a..6647f54b3a 100644 --- a/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx +++ b/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx @@ -6,7 +6,7 @@ import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; import AppReducer from 'types/reducer/app'; import DashboardReducer from 'types/reducer/dashboards'; -import { v4 } from 'uuid'; +import { v4 as uuid } from 'uuid'; import menuItems, { ITEMS } from './menuItems'; import { Card, Container, Text } from './styles'; @@ -29,8 +29,6 @@ function DashboardGraphSlider(): JSX.Element { const onClickHandler = useCallback( async (name: ITEMS) => { try { - const generateWidgetId = v4(); - const getX = (): number => { if (data.layout && data.layout?.length > 0) { const lastIndexX = data.layout[(data.layout?.length || 0) - 1]; @@ -41,7 +39,7 @@ function DashboardGraphSlider(): JSX.Element { await updateDashboard({ data, - generateWidgetId, + generateWidgetId: uuid(), graphType: name, layout: [ ...(data.layout || []), diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/ShareModal.tsx b/frontend/src/container/NewDashboard/DescriptionOfDashboard/ShareModal.tsx new file mode 100644 index 0000000000..d746d42218 --- /dev/null +++ b/frontend/src/container/NewDashboard/DescriptionOfDashboard/ShareModal.tsx @@ -0,0 +1,102 @@ +import { Button, Modal, notification, Typography } from 'antd'; +import Editor from 'components/Editor'; +import React, { useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useCopyToClipboard } from 'react-use'; +import { DashboardData } from 'types/api/dashboard/getAll'; + +import { downloadObjectAsJson } from './util'; + +function ShareModal({ + isJSONModalVisible, + onToggleHandler, + selectedData, +}: ShareModalProps): JSX.Element { + const [jsonValue, setJSONValue] = useState( + JSON.stringify(selectedData, null, 2), + ); + const [isViewJSON, setIsViewJSON] = useState(false); + const { t } = useTranslation(['dashboard', 'common']); + const [state, setCopy] = useCopyToClipboard(); + + useEffect(() => { + if (state.error) { + notification.error({ + message: t('something_went_wrong', { + ns: 'common', + }), + }); + } + + if (state.value) { + notification.success({ + message: t('success', { + ns: 'common', + }), + }); + } + }, [state.error, state.value, t]); + + const GetFooterComponent = useMemo(() => { + if (!isViewJSON) { + return ( + <> + { + setIsViewJSON(true); + }} + > + {t('view_json')} + + + { + downloadObjectAsJson(selectedData, selectedData.title); + }} + > + {t('download_json')} + + > + ); + } + return ( + setCopy(jsonValue)} type="primary"> + {t('copy_to_clipboard')} + + ); + }, [isViewJSON, jsonValue, selectedData, setCopy, t]); + + return ( + { + onToggleHandler(); + setIsViewJSON(false); + }} + width="70vw" + centered + title={t('share', { + ns: 'common', + })} + okText={t('download_json')} + cancelText={t('cancel')} + destroyOnClose + footer={GetFooterComponent} + > + {!isViewJSON ? ( + {t('export_dashboard')} + ) : ( + setJSONValue(value)} value={jsonValue} /> + )} + + ); +} + +interface ShareModalProps { + isJSONModalVisible: boolean; + onToggleHandler: VoidFunction; + selectedData: DashboardData; +} + +export default ShareModal; diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/index.tsx b/frontend/src/container/NewDashboard/DescriptionOfDashboard/index.tsx index 7ee3132d37..bcaa553e62 100644 --- a/frontend/src/container/NewDashboard/DescriptionOfDashboard/index.tsx +++ b/frontend/src/container/NewDashboard/DescriptionOfDashboard/index.tsx @@ -1,8 +1,14 @@ -import { EditOutlined, SaveOutlined } from '@ant-design/icons'; -import { Card, Col, Row, Tag, Typography } from 'antd'; +import { + EditOutlined, + SaveOutlined, + ShareAltOutlined, +} from '@ant-design/icons'; +import { Card, Col, Row, Space, Tag, Typography } from 'antd'; import AddTags from 'container/NewDashboard/DescriptionOfDashboard/AddTags'; import NameOfTheDashboard from 'container/NewDashboard/DescriptionOfDashboard/NameOfTheDashboard'; +import useComponentPermission from 'hooks/useComponentPermission'; import React, { useCallback, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { connect, useSelector } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; import { ThunkDispatch } from 'redux-thunk'; @@ -13,9 +19,11 @@ import { } from 'store/actions'; import { AppState } from 'store/reducers'; import AppActions from 'types/actions'; +import AppReducer from 'types/reducer/app'; import DashboardReducer from 'types/reducer/dashboards'; import Description from './Description'; +import ShareModal from './ShareModal'; import { Button, Container } from './styles'; function DescriptionOfDashboard({ @@ -34,9 +42,14 @@ function DescriptionOfDashboard({ const [updatedTitle, setUpdatedTitle] = useState(title); const [updatedTags, setUpdatedTags] = useState(tags || []); - const [updatedDescription, setUpdtatedDescription] = useState( + const [updatedDescription, setUpdatedDescription] = useState( description || '', ); + const [isJSONModalVisible, isIsJSONModalVisible] = useState(false); + + const { t } = useTranslation('common'); + const { role } = useSelector((state) => state.app); + const [editDashboard] = useComponentPermission(['edit_dashboard'], role); const onClickEditHandler = useCallback(() => { if (isEditMode) { @@ -66,6 +79,10 @@ function DescriptionOfDashboard({ updateDashboardTitleDescriptionTags, ]); + const onToggleHandler = (): void => { + isIsJSONModalVisible((state) => !state); + }; + return ( @@ -87,17 +104,33 @@ function DescriptionOfDashboard({ )} + + + - : } - onClick={onClickEditHandler} - > - {isEditMode ? 'Save' : 'Edit'} - + + }> + {t('share')} + + {editDashboard && ( + : } + onClick={onClickEditHandler} + > + {isEditMode ? t('save') : t('edit')} + + )} + diff --git a/frontend/src/container/NewDashboard/DescriptionOfDashboard/util.ts b/frontend/src/container/NewDashboard/DescriptionOfDashboard/util.ts new file mode 100644 index 0000000000..c86698436d --- /dev/null +++ b/frontend/src/container/NewDashboard/DescriptionOfDashboard/util.ts @@ -0,0 +1,14 @@ +export function downloadObjectAsJson( + exportObj: unknown, + exportName: string, +): void { + const dataStr = `data:text/json;charset=utf-8,${encodeURIComponent( + JSON.stringify(exportObj), + )}`; + const downloadAnchorNode = document.createElement('a'); + downloadAnchorNode.setAttribute('href', dataStr); + downloadAnchorNode.setAttribute('download', `${exportName}.json`); + document.body.appendChild(downloadAnchorNode); // required for firefox + downloadAnchorNode.click(); + downloadAnchorNode.remove(); +} diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraph.tsx b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraph.tsx index 2a6fd02a00..37bf188bb3 100644 --- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraph.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/WidgetGraph.tsx @@ -43,7 +43,7 @@ function WidgetGraph({ } const chartDataSet = getChartData({ - queryData, + queryData: queryData.data, }); return ( diff --git a/frontend/src/container/OrganizationSettings/DeleteMembersDetails/index.tsx b/frontend/src/container/OrganizationSettings/DeleteMembersDetails/index.tsx new file mode 100644 index 0000000000..db51e685b0 --- /dev/null +++ b/frontend/src/container/OrganizationSettings/DeleteMembersDetails/index.tsx @@ -0,0 +1,33 @@ +import { gold } from '@ant-design/colors'; +import { ExclamationCircleTwoTone } from '@ant-design/icons'; +import { Space, Typography } from 'antd'; +import React from 'react'; + +function DeleteMembersDetails({ + name, +}: DeleteMembersDetailsProps): JSX.Element { + return ( + + + + + Are you sure you want to delete {name} + + This will remove all access from dashboards and other features in SigNoz + + + + + ); +} + +interface DeleteMembersDetailsProps { + name: string; +} + +export default DeleteMembersDetails; diff --git a/frontend/src/container/OrganizationSettings/DisplayName/index.tsx b/frontend/src/container/OrganizationSettings/DisplayName/index.tsx new file mode 100644 index 0000000000..394d42b76c --- /dev/null +++ b/frontend/src/container/OrganizationSettings/DisplayName/index.tsx @@ -0,0 +1,99 @@ +import { Button, Input, notification, Space, Typography } from 'antd'; +import editOrg from 'api/user/editOrg'; +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useDispatch, useSelector } from 'react-redux'; +import { Dispatch } from 'redux'; +import { AppState } from 'store/reducers'; +import AppActions from 'types/actions'; +import { UPDATE_ORG_NAME } from 'types/actions/app'; +import AppReducer, { User } from 'types/reducer/app'; + +function DisplayName({ + index, + id: orgId, + isAnonymous, +}: DisplayNameProps): JSX.Element { + const { t } = useTranslation(['organizationsettings', 'common']); + const { org } = useSelector((state) => state.app); + const { name } = (org || [])[index]; + const [orgName, setOrgName] = useState(name); + const [isLoading, setIsLoading] = useState(false); + const dispatch = useDispatch>(); + + const onClickHandler = async (): Promise => { + try { + setIsLoading(true); + const { statusCode, error } = await editOrg({ + isAnonymous, + name: orgName, + orgId, + }); + if (statusCode === 200) { + notification.success({ + message: t('success', { + ns: 'common', + }), + }); + dispatch({ + type: UPDATE_ORG_NAME, + payload: { + index, + name: orgName, + }, + }); + } else { + notification.error({ + message: + error || + t('something_went_wrong', { + ns: 'common', + }), + }); + } + setIsLoading(false); + } catch (error) { + setIsLoading(false); + notification.error({ + message: t('something_went_wrong', { + ns: 'common', + }), + }); + } + }; + + if (!org) { + return ; + } + + return ( + + {t('display_name')} + + setOrgName(e.target.value)} + size="large" + placeholder={t('signoz')} + disabled={isLoading} + /> + + Change Org Name + + + + ); +} + +interface DisplayNameProps { + index: number; + id: User['userId']; + isAnonymous: boolean; +} + +export default DisplayName; diff --git a/frontend/src/container/OrganizationSettings/EditMembersDetails/index.tsx b/frontend/src/container/OrganizationSettings/EditMembersDetails/index.tsx new file mode 100644 index 0000000000..9b3dfcfde2 --- /dev/null +++ b/frontend/src/container/OrganizationSettings/EditMembersDetails/index.tsx @@ -0,0 +1,167 @@ +import { CopyOutlined } from '@ant-design/icons'; +import { Button, Input, notification, Select, Space, Tooltip } from 'antd'; +import getResetPasswordToken from 'api/user/getResetPasswordToken'; +import ROUTES from 'constants/routes'; +import React, { useCallback, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useCopyToClipboard } from 'react-use'; +import { ROLES } from 'types/roles'; + +import { InputGroup, SelectDrawer, Title } from './styles'; + +const { Option } = Select; + +function EditMembersDetails({ + emailAddress, + name, + role, + setEmailAddress, + setName, + setRole, + id, +}: EditMembersDetailsProps): JSX.Element { + const [passwordLink, setPasswordLink] = useState(''); + + const { t } = useTranslation(['common']); + const [isLoading, setIsLoading] = useState(false); + const [state, copyToClipboard] = useCopyToClipboard(); + + const getPasswordLink = (token: string): string => { + return `${window.location.origin}${ROUTES.PASSWORD_RESET}?token=${token}`; + }; + + const onChangeHandler = useCallback( + (setFunc: React.Dispatch>, value: string) => { + setFunc(value); + }, + [], + ); + + useEffect(() => { + if (state.error) { + notification.error({ + message: t('something_went_wrong'), + }); + } + + if (state.value) { + notification.success({ + message: t('success'), + }); + } + }, [state.error, state.value, t]); + + const onPasswordChangeHandler = useCallback((event) => { + setPasswordLink(event.target.value); + }, []); + + const onGeneratePasswordHandler = async (): Promise => { + try { + setIsLoading(true); + const response = await getResetPasswordToken({ + userId: id || '', + }); + + if (response.statusCode === 200) { + setPasswordLink(getPasswordLink(response.payload.token)); + } else { + notification.error({ + message: + response.error || + t('something_went_wrong', { + ns: 'common', + }), + }); + } + setIsLoading(false); + } catch (error) { + setIsLoading(false); + + notification.error({ + message: t('something_went_wrong', { + ns: 'common', + }), + }); + } + }; + + return ( + + + Email address + + onChangeHandler(setEmailAddress, event.target.value) + } + disabled={isLoading} + value={emailAddress} + /> + + + Name (optional) + onChangeHandler(setName, event.target.value)} + value={name} + disabled={isLoading} + /> + + + Role + { + if (typeof value === 'string') { + setRole(value as ROLES); + } + }} + disabled={isLoading} + > + ADMIN + VIEWER + EDITOR + + + + + Generate Reset Password link + + {passwordLink && ( + + + + } + onClick={(): void => copyToClipboard(passwordLink)} + /> + + + )} + + ); +} + +interface EditMembersDetailsProps { + emailAddress: string; + name: string; + role: ROLES; + setEmailAddress: React.Dispatch>; + setName: React.Dispatch>; + setRole: React.Dispatch>; + id: string; +} + +export default EditMembersDetails; diff --git a/frontend/src/container/OrganizationSettings/EditMembersDetails/styles.ts b/frontend/src/container/OrganizationSettings/EditMembersDetails/styles.ts new file mode 100644 index 0000000000..28ac29180b --- /dev/null +++ b/frontend/src/container/OrganizationSettings/EditMembersDetails/styles.ts @@ -0,0 +1,16 @@ +import { Select, Typography } from 'antd'; +import styled from 'styled-components'; + +export const SelectDrawer = styled(Select)` + width: 120px; +`; + +export const Title = styled(Typography)` + width: 7rem; +`; + +export const InputGroup = styled.div` + display: flex; + flex-direction: row; + align-items: center; +`; diff --git a/frontend/src/container/OrganizationSettings/InviteTeamMembers/index.tsx b/frontend/src/container/OrganizationSettings/InviteTeamMembers/index.tsx new file mode 100644 index 0000000000..6a4910de59 --- /dev/null +++ b/frontend/src/container/OrganizationSettings/InviteTeamMembers/index.tsx @@ -0,0 +1,108 @@ +import { PlusOutlined } from '@ant-design/icons'; +import { Button, Form, Input, Select, Space, Typography } from 'antd'; +import React, { useCallback, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { InviteTeamMembersProps } from '../PendingInvitesContainer/index'; +import { SelectDrawer, TitleWrapper } from './styles'; + +const { Option } = Select; + +function InviteTeamMembers({ allMembers, setAllMembers }: Props): JSX.Element { + const { t } = useTranslation('organizationsettings'); + + useEffect(() => { + return (): void => { + setAllMembers([ + { + email: '', + name: '', + role: 'VIEWER', + }, + ]); + }; + }, [setAllMembers]); + + const onAddHandler = (): void => { + setAllMembers((state) => [ + ...state, + { + email: '', + name: '', + role: 'VIEWER', + }, + ]); + }; + + const onChangeHandler = useCallback( + (value: string, index: number, type: string): void => { + setAllMembers((prev) => { + return [ + ...prev.slice(0, index), + { + ...prev[index], + [type]: value, + }, + ...prev.slice(index, prev.length - 1), + ]; + }); + }, + [setAllMembers], + ); + + return ( + <> + + {t('email_address')} + {t('name_optional')} + {t('role')} + + + + {allMembers.map((e, index) => ( + + { + onChangeHandler(event.target.value, index, 'email'); + }} + required + /> + { + onChangeHandler(event.target.value, index, 'name'); + }} + required + /> + { + if (typeof value === 'string') { + onChangeHandler(value, index, 'role'); + } + }} + > + ADMIN + VIEWER + EDITOR + + + ))} + } type="default"> + {t('add_another_team_member')} + + + + > + ); +} + +interface Props { + allMembers: InviteTeamMembersProps[]; + setAllMembers: React.Dispatch>; +} + +export default InviteTeamMembers; diff --git a/frontend/src/container/OrganizationSettings/InviteTeamMembers/styles.ts b/frontend/src/container/OrganizationSettings/InviteTeamMembers/styles.ts new file mode 100644 index 0000000000..1426130972 --- /dev/null +++ b/frontend/src/container/OrganizationSettings/InviteTeamMembers/styles.ts @@ -0,0 +1,15 @@ +import { Select } from 'antd'; +import styled from 'styled-components'; + +export const SelectDrawer = styled(Select)` + width: 120px; +`; + +export const TitleWrapper = styled.div` + display: flex; + margin-bottom: 1rem; + + > article { + min-width: 11rem; + } +`; diff --git a/frontend/src/container/OrganizationSettings/Members/index.tsx b/frontend/src/container/OrganizationSettings/Members/index.tsx new file mode 100644 index 0000000000..5951fb038b --- /dev/null +++ b/frontend/src/container/OrganizationSettings/Members/index.tsx @@ -0,0 +1,327 @@ +import { Button, Modal, notification, Space, Typography } from 'antd'; +import Table, { ColumnsType } from 'antd/lib/table'; +import deleteUser from 'api/user/deleteUser'; +import editUserApi from 'api/user/editUser'; +import getOrgUser from 'api/user/getOrgUser'; +import updateRole from 'api/user/updateRole'; +import dayjs from 'dayjs'; +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useQuery } from 'react-query'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import AppReducer from 'types/reducer/app'; +import { ROLES } from 'types/roles'; + +import DeleteMembersDetails from '../DeleteMembersDetails'; +import EditMembersDetails from '../EditMembersDetails'; + +function UserFunction({ + setDataSource, + accessLevel, + name, + email, + id, +}: UserFunctionProps): JSX.Element { + const [isModalVisible, setIsModalVisible] = useState(false); + const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); + + const onModalToggleHandler = ( + func: React.Dispatch>, + value: boolean, + ): void => { + func(value); + }; + + const [emailAddress, setEmailAddress] = useState(email); + const [updatedName, setUpdatedName] = useState(name); + const [role, setRole] = useState(accessLevel); + const { t } = useTranslation(['common']); + const [isDeleteLoading, setIsDeleteLoading] = useState(false); + const [isUpdateLoading, setIsUpdateLoading] = useState(false); + + const onUpdateDetailsHandler = (): void => { + setDataSource((data) => { + const index = data.findIndex((e) => e.id === id); + if (index !== -1) { + const current = data[index]; + + const updatedData: DataType[] = [ + ...data.slice(0, index), + { + ...current, + name: updatedName, + accessLevel: role, + email: emailAddress, + }, + ...data.slice(index + 1, data.length), + ]; + + return updatedData; + } + return data; + }); + }; + + const onDelete = (): void => { + setDataSource((source) => { + const index = source.findIndex((e) => e.id === id); + + if (index !== -1) { + const updatedData: DataType[] = [ + ...source.slice(0, index), + ...source.slice(index + 1, source.length), + ]; + + return updatedData; + } + return source; + }); + }; + + const onDeleteHandler = async (): Promise => { + try { + setIsDeleteLoading(true); + const response = await deleteUser({ + userId: id, + }); + + if (response.statusCode === 200) { + onDelete(); + notification.success({ + message: t('success', { + ns: 'common', + }), + }); + setIsDeleteModalVisible(false); + } else { + notification.error({ + message: + response.error || + t('something_went_wrong', { + ns: 'common', + }), + }); + } + setIsDeleteLoading(false); + } catch (error) { + setIsDeleteLoading(false); + + notification.error({ + message: t('something_went_wrong', { + ns: 'common', + }), + }); + } + }; + + const onInviteMemberHandler = async (): Promise => { + try { + setIsUpdateLoading(true); + const [editUserResponse, updateRoleResponse] = await Promise.all([ + editUserApi({ + userId: id, + name: updatedName, + }), + updateRole({ + group_name: role, + userId: id, + }), + ]); + + if ( + editUserResponse.statusCode === 200 && + updateRoleResponse.statusCode === 200 + ) { + onUpdateDetailsHandler(); + notification.success({ + message: t('success', { + ns: 'common', + }), + }); + } else { + notification.error({ + message: + editUserResponse.error || + updateRoleResponse.error || + t('something_went_wrong', { + ns: 'common', + }), + }); + } + setIsUpdateLoading(false); + } catch (error) { + notification.error({ + message: t('something_went_wrong', { + ns: 'common', + }), + }); + setIsUpdateLoading(false); + } + }; + + return ( + <> + + onModalToggleHandler(setIsModalVisible, true)} + > + Edit + + onModalToggleHandler(setIsDeleteModalVisible, true)} + > + Delete + + + onModalToggleHandler(setIsModalVisible, false)} + onCancel={(): void => onModalToggleHandler(setIsModalVisible, false)} + centered + destroyOnClose + footer={[ + onModalToggleHandler(setIsModalVisible, false)} + type="default" + > + Cancel + , + + Update Details + , + ]} + > + + + onModalToggleHandler(setIsDeleteModalVisible, false)} + centered + confirmLoading={isDeleteLoading} + > + + + > + ); +} + +function Members(): JSX.Element { + const { org } = useSelector((state) => state.app); + const { status, data } = useQuery({ + queryFn: () => + getOrgUser({ + orgId: (org || [])[0].id, + }), + queryKey: 'getOrgUser', + }); + + const [dataSource, setDataSource] = useState([]); + + useEffect(() => { + if (status === 'success' && data?.payload && Array.isArray(data.payload)) { + const updatedData: DataType[] = data?.payload?.map((e) => ({ + accessLevel: e.role, + email: e.email, + id: String(e.id), + joinedOn: String(e.createdAt), + name: e.name, + })); + setDataSource(updatedData); + } + }, [data?.payload, status]); + + const columns: ColumnsType = [ + { + title: 'Name', + dataIndex: 'name', + key: 'name', + }, + { + title: 'Emails', + dataIndex: 'email', + key: 'email', + }, + { + title: 'Access Level', + dataIndex: 'accessLevel', + key: 'accessLevel', + }, + { + title: 'Joined On', + dataIndex: 'joinedOn', + key: 'joinedOn', + render: (_, record): JSX.Element => { + const { joinedOn } = record; + return ( + + {dayjs.unix(Number(joinedOn)).format('MMMM DD,YYYY')} + + ); + }, + }, + { + title: 'Action', + dataIndex: 'action', + render: (_, record): JSX.Element => ( + + ), + }, + ]; + + return ( + + Members + + + ); +} + +interface DataType { + id: string; + name: string; + email: string; + accessLevel: ROLES; + joinedOn: string; +} + +interface UserFunctionProps extends DataType { + setDataSource: React.Dispatch>; +} + +export default Members; diff --git a/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx b/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx new file mode 100644 index 0000000000..ffd7d275f3 --- /dev/null +++ b/frontend/src/container/OrganizationSettings/PendingInvitesContainer/index.tsx @@ -0,0 +1,287 @@ +import { PlusOutlined } from '@ant-design/icons'; +import { Button, Modal, notification, Space, Typography } from 'antd'; +import Table, { ColumnsType } from 'antd/lib/table'; +import deleteInvite from 'api/user/deleteInvite'; +import getPendingInvites from 'api/user/getPendingInvites'; +import sendInvite from 'api/user/sendInvite'; +import { INVITE_MEMBERS_HASH } from 'constants/app'; +import ROUTES from 'constants/routes'; +import React, { useCallback, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useQuery } from 'react-query'; +import { useLocation } from 'react-router-dom'; +import { useCopyToClipboard } from 'react-use'; +import { PayloadProps } from 'types/api/user/getPendingInvites'; +import { ROLES } from 'types/roles'; + +import InviteTeamMembers from '../InviteTeamMembers'; +import { TitleWrapper } from './styles'; + +function PendingInvitesContainer(): JSX.Element { + const [ + isInviteTeamMemberModalOpen, + setIsInviteTeamMemberModalOpen, + ] = useState(false); + const [isInvitingMembers, setIsInvitingMembers] = useState(false); + const { t } = useTranslation(['organizationsettings', 'common']); + const [state, setText] = useCopyToClipboard(); + + useEffect(() => { + if (state.error) { + notification.error({ + message: state.error.message, + }); + } + + if (state.value) { + notification.success({ + message: t('success', { + ns: 'common', + }), + }); + } + }, [state.error, state.value, t]); + + const getPendingInvitesResponse = useQuery({ + queryFn: () => getPendingInvites(), + queryKey: 'getPendingInvites', + }); + + const toggleModal = (value: boolean): void => { + setIsInviteTeamMemberModalOpen(value); + }; + + const [allMembers, setAllMembers] = useState([ + { + email: '', + name: '', + role: 'VIEWER', + }, + ]); + + const [dataSource, setDataSource] = useState([]); + + const { hash } = useLocation(); + + const getParsedInviteData = useCallback((payload: PayloadProps = []) => { + return payload?.map((data) => ({ + key: data.createdAt, + name: data.name, + email: data.email, + accessLevel: data.role, + inviteLink: `${window.location.origin}${ROUTES.SIGN_UP}?token=${data.token}`, + })); + }, []); + + useEffect(() => { + if (hash === INVITE_MEMBERS_HASH) { + toggleModal(true); + } + }, [hash]); + + useEffect(() => { + if ( + getPendingInvitesResponse.status === 'success' && + getPendingInvitesResponse?.data?.payload + ) { + const data = getParsedInviteData( + getPendingInvitesResponse?.data?.payload || [], + ); + setDataSource(data); + } + }, [ + getParsedInviteData, + getPendingInvitesResponse?.data?.payload, + getPendingInvitesResponse.status, + ]); + + const onRevokeHandler = async (email: string): Promise => { + try { + const response = await deleteInvite({ + email, + }); + if (response.statusCode === 200) { + // remove from the client data + const index = dataSource.findIndex((e) => e.email === email); + + if (index !== -1) { + setDataSource([ + ...dataSource.slice(0, index), + ...dataSource.slice(index + 1, dataSource.length), + ]); + } + notification.success({ + message: t('success', { + ns: 'common', + }), + }); + } else { + notification.error({ + message: + response.error || + t('something_went_wrong', { + ns: 'common', + }), + }); + } + } catch (error) { + notification.error({ + message: t('something_went_wrong', { + ns: 'common', + }), + }); + } + }; + + const columns: ColumnsType = [ + { + title: 'Name', + dataIndex: 'name', + key: 'name', + }, + { + title: 'Emails', + dataIndex: 'email', + key: 'email', + }, + { + title: 'Access Level', + dataIndex: 'accessLevel', + key: 'accessLevel', + }, + { + title: 'Invite Link', + dataIndex: 'inviteLink', + key: 'Invite Link', + ellipsis: true, + }, + { + title: 'Action', + dataIndex: 'action', + key: 'Action', + render: (_, record): JSX.Element => ( + + => onRevokeHandler(record.email)} + > + Revoke + + { + setText(record.inviteLink); + }} + > + Copy Invite Link + + + ), + }, + ]; + + const onInviteClickHandler = async (): Promise => { + try { + setIsInvitingMembers(true); + allMembers.forEach( + async (members): Promise => { + const { error, statusCode } = await sendInvite({ + email: members.email, + name: members.name, + role: members.role, + }); + + if (statusCode !== 200) { + notification.error({ + message: + error || + t('something_went_wrong', { + ns: 'common', + }), + }); + } + }, + ); + + setTimeout(async () => { + const { data, status } = await getPendingInvitesResponse.refetch(); + if (status === 'success' && data.payload) { + setDataSource(getParsedInviteData(data?.payload || [])); + } + setIsInvitingMembers(false); + toggleModal(false); + }, 2000); + } catch (error) { + notification.error({ + message: t('something_went_wrong', { + ns: 'common', + }), + }); + } + }; + + return ( + + toggleModal(false)} + centered + destroyOnClose + footer={[ + toggleModal(false)} type="default"> + {t('cancel', { + ns: 'common', + })} + , + + {t('invite_team_members')} + , + ]} + > + + + + + + {t('pending_invites')} + } + type="primary" + onClick={(): void => { + toggleModal(true); + }} + > + {t('invite_members')} + + + + + + ); +} + +export interface InviteTeamMembersProps { + email: string; + name: string; + role: ROLES; +} + +interface DataProps { + key: number; + name: string; + email: string; + accessLevel: ROLES; + inviteLink: string; +} +export default PendingInvitesContainer; diff --git a/frontend/src/container/OrganizationSettings/PendingInvitesContainer/styles.tsx b/frontend/src/container/OrganizationSettings/PendingInvitesContainer/styles.tsx new file mode 100644 index 0000000000..1bd17cf950 --- /dev/null +++ b/frontend/src/container/OrganizationSettings/PendingInvitesContainer/styles.tsx @@ -0,0 +1,8 @@ +import styled from 'styled-components'; + +export const TitleWrapper = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +`; diff --git a/frontend/src/container/OrganizationSettings/index.tsx b/frontend/src/container/OrganizationSettings/index.tsx new file mode 100644 index 0000000000..327495ab2a --- /dev/null +++ b/frontend/src/container/OrganizationSettings/index.tsx @@ -0,0 +1,38 @@ +import { Divider, Space } from 'antd'; +import React from 'react'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import AppReducer from 'types/reducer/app'; + +import DisplayName from './DisplayName'; +import Members from './Members'; +import PendingInvitesContainer from './PendingInvitesContainer'; + +function OrganizationSettings(): JSX.Element { + const { org } = useSelector((state) => state.app); + + if (!org) { + return ; + } + + return ( + <> + + {org.map((e, index) => ( + + ))} + + + + + + > + ); +} + +export default OrganizationSettings; diff --git a/frontend/src/container/ResetPassword/index.tsx b/frontend/src/container/ResetPassword/index.tsx new file mode 100644 index 0000000000..d058a33969 --- /dev/null +++ b/frontend/src/container/ResetPassword/index.tsx @@ -0,0 +1,157 @@ +import { Button, Input, notification, Typography } from 'antd'; +import resetPasswordApi from 'api/user/resetPassword'; +import { Logout } from 'api/utils'; +import WelcomeLeftContainer from 'components/WelcomeLeftContainer'; +import ROUTES from 'constants/routes'; +import history from 'lib/history'; +import { Label } from 'pages/SignUp/styles'; +import React, { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useLocation } from 'react-use'; + +import { ButtonContainer, FormWrapper } from './styles'; + +const { Title } = Typography; + +function ResetPassword({ version }: ResetPasswordProps): JSX.Element { + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [confirmPasswordError, setConfirmPasswordError] = useState( + false, + ); + const [loading, setLoading] = useState(false); + const { t } = useTranslation(['common']); + const { search } = useLocation(); + const params = new URLSearchParams(search); + const token = params.get('token'); + + useEffect(() => { + if (!token) { + Logout(); + history.push(ROUTES.LOGIN); + } + }, [token]); + + const setState = ( + value: string, + setFunction: React.Dispatch>, + ): void => { + setFunction(value); + }; + + const handleSubmit: React.FormEventHandler = async ( + event, + ): Promise => { + try { + setLoading(true); + event.preventDefault(); + event.persist(); + + const response = await resetPasswordApi({ + password, + token: token || '', + }); + + if (response.statusCode === 200) { + notification.success({ + message: t('success', { + ns: 'common', + }), + }); + history.push(ROUTES.LOGIN); + } else { + notification.error({ + message: + response.error || + t('something_went_wrong', { + ns: 'common', + }), + }); + } + + setLoading(false); + } catch (error) { + setLoading(false); + notification.error({ + message: t('something_went_wrong', { + ns: 'common', + }), + }); + } + }; + + return ( + + + + Reset Your Password + + + Password + { + setState(e.target.value, setPassword); + }} + required + id="currentPassword" + /> + + + Confirm Password + { + const updateValue = e.target.value; + setState(updateValue, setConfirmPassword); + if (password !== updateValue) { + setConfirmPasswordError(true); + } else { + setConfirmPasswordError(false); + } + }} + required + id="UpdatePassword" + /> + + {confirmPasswordError && ( + + Passwords don’t match. Please try again + + )} + + + + + Get Started + + + + + + ); +} + +interface ResetPasswordProps { + version: string; +} + +export default ResetPassword; diff --git a/frontend/src/container/ResetPassword/styles.ts b/frontend/src/container/ResetPassword/styles.ts new file mode 100644 index 0000000000..8405f31669 --- /dev/null +++ b/frontend/src/container/ResetPassword/styles.ts @@ -0,0 +1,16 @@ +import { Card } from 'antd'; +import styled from 'styled-components'; + +export const FormWrapper = styled(Card)` + display: flex; + justify-content: center; + max-width: 432px; + flex: 1; +`; + +export const ButtonContainer = styled.div` + margin-top: 1.8125rem; + display: flex; + justify-content: center; + align-items: center; +`; diff --git a/frontend/src/container/SideNav/index.tsx b/frontend/src/container/SideNav/index.tsx index 44bb2201e8..a9fe78ce07 100644 --- a/frontend/src/container/SideNav/index.tsx +++ b/frontend/src/container/SideNav/index.tsx @@ -4,68 +4,37 @@ import getLocalStorageKey from 'api/browser/localstorage/get'; import { IS_SIDEBAR_COLLAPSED } from 'constants/app'; import ROUTES from 'constants/routes'; import history from 'lib/history'; -import setTheme, { AppMode } from 'lib/theme/setTheme'; import React, { useCallback, useLayoutEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { connect, useDispatch, useSelector } from 'react-redux'; -import { NavLink, useLocation } from 'react-router-dom'; -import { bindActionCreators } from 'redux'; -import { ThunkDispatch } from 'redux-thunk'; -import { ToggleDarkMode } from 'store/actions'; +import { useDispatch, useSelector } from 'react-redux'; +import { useLocation } from 'react-router-dom'; import { SideBarCollapse } from 'store/actions/app'; import { AppState } from 'store/reducers'; -import AppActions from 'types/actions'; import AppReducer from 'types/reducer/app'; import menus from './menuItems'; import Slack from './Slack'; import { - Logo, RedDot, Sider, SlackButton, SlackMenuItemContainer, - ThemeSwitcherWrapper, - ToggleButton, VersionContainer, } from './styles'; -function SideNav({ toggleDarkMode }: Props): JSX.Element { +function SideNav(): JSX.Element { const dispatch = useDispatch(); const [collapsed, setCollapsed] = useState( getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true', ); - const { - isDarkMode, - currentVersion, - latestVersion, - isCurrentVersionError, - } = useSelector((state) => state.app); + const { currentVersion, latestVersion, isCurrentVersionError } = useSelector< + AppState, + AppReducer + >((state) => state.app); const { pathname } = useLocation(); const { t } = useTranslation(''); - const toggleTheme = useCallback(() => { - const preMode: AppMode = isDarkMode ? 'lightMode' : 'darkMode'; - setTheme(preMode); - - const id: AppMode = preMode; - const { head } = document; - const link = document.createElement('link'); - link.rel = 'stylesheet'; - link.type = 'text/css'; - link.href = !isDarkMode ? '/css/antd.dark.min.css' : '/css/antd.min.css'; - link.media = 'all'; - link.id = id; - head.appendChild(link); - - link.onload = (): void => { - toggleDarkMode(); - const prevNode = document.getElementById('appMode'); - prevNode?.remove(); - }; - }, [toggleDarkMode, isDarkMode]); - const onCollapse = useCallback(() => { setCollapsed((collapsed) => !collapsed); }, []); @@ -121,17 +90,6 @@ function SideNav({ toggleDarkMode }: Props): JSX.Element { return ( - - - - - - - void; -} - -const mapDispatchToProps = ( - dispatch: ThunkDispatch, -): DispatchProps => ({ - toggleDarkMode: bindActionCreators(ToggleDarkMode, dispatch), -}); - -type Props = DispatchProps; - -export default connect(null, mapDispatchToProps)(SideNav); +export default SideNav; diff --git a/frontend/src/container/SideNav/menuItems.ts b/frontend/src/container/SideNav/menuItems.ts index 1e1cb0c49e..3b6a186b8d 100644 --- a/frontend/src/container/SideNav/menuItems.ts +++ b/frontend/src/container/SideNav/menuItems.ts @@ -3,6 +3,7 @@ import { AlignLeftOutlined, ApiOutlined, BarChartOutlined, + BugOutlined, DashboardFilled, DeploymentUnitOutlined, LineChartOutlined, @@ -24,13 +25,18 @@ const menus: SidebarMenu[] = [ { Icon: DashboardFilled, to: ROUTES.ALL_DASHBOARD, - name: 'Dashboard', + name: 'Dashboards', }, { Icon: AlertOutlined, to: ROUTES.LIST_ALL_ALERT, name: 'Alerts', }, + { + Icon: BugOutlined, + to: ROUTES.ALL_ERROR, + name: 'Exceptions', + }, { to: ROUTES.SERVICE_MAP, name: 'Service Map', diff --git a/frontend/src/container/SideNav/styles.ts b/frontend/src/container/SideNav/styles.ts index 975aa65ba9..3d72951c99 100644 --- a/frontend/src/container/SideNav/styles.ts +++ b/frontend/src/container/SideNav/styles.ts @@ -1,22 +1,9 @@ -import { Layout, Switch, Typography } from 'antd'; +import { Layout, Typography } from 'antd'; import { StyledCSS } from 'container/GantChart/Trace/styles'; import styled, { css } from 'styled-components'; const { Sider: SiderComponent } = Layout; -export const ThemeSwitcherWrapper = styled.div` - display: flex; - justify-content: center; - margin-top: 24px; - margin-bottom: 16px; -`; - -export const Logo = styled.img` - width: 100px; - margin: 9% 5% 5% 10%; - display: ${({ collapsed }): string => (!collapsed ? 'block' : 'none')}; -`; - interface LogoProps { collapsed: boolean; index: number; @@ -32,17 +19,6 @@ export const Sider = styled(SiderComponent)` } `; -interface DarkModeProps { - checked?: boolean; - defaultChecked?: boolean; -} - -export const ToggleButton = styled(Switch)` - &&& { - background: ${({ checked }): string => (checked === false ? 'grey' : '')}; - } -`; - export const SlackButton = styled(Typography)` &&& { margin-left: 1rem; diff --git a/frontend/src/container/Timeline/index.tsx b/frontend/src/container/Timeline/index.tsx index 0bee6414a1..7a9d6a0a65 100644 --- a/frontend/src/container/Timeline/index.tsx +++ b/frontend/src/container/Timeline/index.tsx @@ -37,11 +37,10 @@ function Timeline({ }); let intervalUnit = INTERVAL_UNITS[0]; - for (let idx = 0; idx < INTERVAL_UNITS.length; idx += 1) { + for (let idx = INTERVAL_UNITS.length - 1; idx >= 0; idx -= 1) { const standardInterval = INTERVAL_UNITS[idx]; - if (baseSpread * standardInterval.multiplier < 1) { - const index = idx; - if (index > 1) intervalUnit = INTERVAL_UNITS[index - 1]; + if (baseSpread * standardInterval.multiplier >= 1) { + intervalUnit = INTERVAL_UNITS[idx]; break; } } diff --git a/frontend/src/container/Header/Breadcrumbs/index.tsx b/frontend/src/container/TopNav/Breadcrumbs/index.tsx similarity index 89% rename from frontend/src/container/Header/Breadcrumbs/index.tsx rename to frontend/src/container/TopNav/Breadcrumbs/index.tsx index d88384b75c..25ff730711 100644 --- a/frontend/src/container/Header/Breadcrumbs/index.tsx +++ b/frontend/src/container/TopNav/Breadcrumbs/index.tsx @@ -11,7 +11,11 @@ const breadcrumbNameMap = { [ROUTES.INSTRUMENTATION]: 'Add instrumentation', [ROUTES.SETTINGS]: 'Settings', [ROUTES.DASHBOARD]: 'Dashboard', + [ROUTES.ALL_ERROR]: 'Exceptions', [ROUTES.VERSION]: 'Status', + [ROUTES.ORG_SETTINGS]: 'Organization Settings', + [ROUTES.MY_SETTINGS]: 'My Settings', + [ROUTES.ERROR_DETAIL]: 'Errors', }; function ShowBreadcrumbs(props: RouteComponentProps): JSX.Element { diff --git a/frontend/src/container/Header/CustomDateTimeModal/index.tsx b/frontend/src/container/TopNav/CustomDateTimeModal/index.tsx similarity index 100% rename from frontend/src/container/Header/CustomDateTimeModal/index.tsx rename to frontend/src/container/TopNav/CustomDateTimeModal/index.tsx diff --git a/frontend/src/container/Header/DateTimeSelection/Refresh.tsx b/frontend/src/container/TopNav/DateTimeSelection/Refresh.tsx similarity index 100% rename from frontend/src/container/Header/DateTimeSelection/Refresh.tsx rename to frontend/src/container/TopNav/DateTimeSelection/Refresh.tsx diff --git a/frontend/src/container/Header/DateTimeSelection/config.ts b/frontend/src/container/TopNav/DateTimeSelection/config.ts similarity index 100% rename from frontend/src/container/Header/DateTimeSelection/config.ts rename to frontend/src/container/TopNav/DateTimeSelection/config.ts diff --git a/frontend/src/container/Header/DateTimeSelection/index.tsx b/frontend/src/container/TopNav/DateTimeSelection/index.tsx similarity index 100% rename from frontend/src/container/Header/DateTimeSelection/index.tsx rename to frontend/src/container/TopNav/DateTimeSelection/index.tsx diff --git a/frontend/src/container/Header/DateTimeSelection/styles.ts b/frontend/src/container/TopNav/DateTimeSelection/styles.ts similarity index 100% rename from frontend/src/container/Header/DateTimeSelection/styles.ts rename to frontend/src/container/TopNav/DateTimeSelection/styles.ts diff --git a/frontend/src/container/TopNav/index.tsx b/frontend/src/container/TopNav/index.tsx new file mode 100644 index 0000000000..ffd1b28175 --- /dev/null +++ b/frontend/src/container/TopNav/index.tsx @@ -0,0 +1,55 @@ +import { Col } from 'antd'; +import ROUTES from 'constants/routes'; +import history from 'lib/history'; +import React from 'react'; +import { matchPath } from 'react-router-dom'; + +import ShowBreadcrumbs from './Breadcrumbs'; +import DateTimeSelector from './DateTimeSelection'; +import { Container } from './styles'; + +const routesToSkip = [ + ROUTES.SETTINGS, + ROUTES.LIST_ALL_ALERT, + ROUTES.TRACE_DETAIL, + ROUTES.ALL_CHANNELS, + ROUTES.USAGE_EXPLORER, + ROUTES.INSTRUMENTATION, + ROUTES.VERSION, + ROUTES.ALL_DASHBOARD, + ROUTES.ORG_SETTINGS, + ROUTES.ERROR_DETAIL, +]; + +function TopNav(): JSX.Element | null { + if (history.location.pathname === ROUTES.SIGN_UP) { + return null; + } + + const checkRouteExists = (currentPath: string): boolean => { + for (let i = 0; i < routesToSkip.length; i += 1) { + if ( + matchPath(currentPath, { path: routesToSkip[i], exact: true, strict: true }) + ) { + return true; + } + } + return false; + }; + + return ( + + + + + + {!checkRouteExists(history.location.pathname) && ( + + + + )} + + ); +} + +export default TopNav; diff --git a/frontend/src/container/TopNav/styles.ts b/frontend/src/container/TopNav/styles.ts new file mode 100644 index 0000000000..feda027d24 --- /dev/null +++ b/frontend/src/container/TopNav/styles.ts @@ -0,0 +1,9 @@ +import { Row } from 'antd'; +import styled from 'styled-components'; + +export const Container = styled(Row)` + &&& { + margin-top: 2rem; + min-height: 8vh; + } +`; diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx b/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx index 1c0c5fae75..65a9a60112 100644 --- a/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx +++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/Common/Checkbox.tsx @@ -120,6 +120,8 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element { userSelected: preUserSelectedMap, isFilterExclude: preIsFilterExclude, order: spansAggregate.order, + orderParam: spansAggregate.orderParam, + pageSize: spansAggregate.pageSize, }, }); @@ -133,6 +135,8 @@ function CheckBoxComponent(props: CheckBoxProps): JSX.Element { preIsFilterExclude, preUserSelectedMap, spansAggregate.order, + spansAggregate.pageSize, + spansAggregate.orderParam, ); } else { setIsLoading(false); diff --git a/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx index e6d44f3b61..a2a5d163a1 100644 --- a/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx +++ b/frontend/src/container/Trace/Filters/Panel/PanelBody/Duration/index.tsx @@ -118,6 +118,8 @@ function Duration(): JSX.Element { userSelected: preUserSelected, isFilterExclude, order: spansAggregate.order, + pageSize: spansAggregate.pageSize, + orderParam: spansAggregate.orderParam, }, }); @@ -129,6 +131,8 @@ function Duration(): JSX.Element { isFilterExclude, userSelectedFilter, spansAggregate.order, + spansAggregate.pageSize, + spansAggregate.orderParam, ); } }; diff --git a/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx b/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx index 9d2b9b1da1..7c87f42531 100644 --- a/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx +++ b/frontend/src/container/Trace/Filters/Panel/PanelHeading/index.tsx @@ -102,6 +102,8 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { userSelected: getPreUserSelected, isFilterExclude, order: spansAggregate.order, + pageSize: spansAggregate.pageSize, + orderParam: spansAggregate.orderParam, }, }); @@ -113,6 +115,8 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { isFilterExclude, getPreUserSelected, spansAggregate.order, + spansAggregate.pageSize, + spansAggregate.orderParam, ); } else { notification.error({ @@ -151,6 +155,8 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { userSelected: userSelectedFilter, isFilterExclude, order: spansAggregate.order, + pageSize: spansAggregate.pageSize, + orderParam: spansAggregate.orderParam, }, }); @@ -162,6 +168,8 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { isFilterExclude, userSelectedFilter, spansAggregate.order, + spansAggregate.pageSize, + spansAggregate.orderParam, ); }; @@ -200,6 +208,8 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { userSelected: preUserSelected, isFilterExclude: postIsFilterExclude, order: spansAggregate.order, + pageSize: spansAggregate.pageSize, + orderParam: spansAggregate.orderParam, }, }); @@ -211,6 +221,8 @@ function PanelHeading(props: PanelHeadingProps): JSX.Element { postIsFilterExclude, preUserSelected, spansAggregate.order, + spansAggregate.pageSize, + spansAggregate.orderParam, ); } else { notification.error({ diff --git a/frontend/src/container/Trace/Search/AllTags/Tag/TagKey.tsx b/frontend/src/container/Trace/Search/AllTags/Tag/TagKey.tsx index aa6f95a4e6..b57b4fc361 100644 --- a/frontend/src/container/Trace/Search/AllTags/Tag/TagKey.tsx +++ b/frontend/src/container/Trace/Search/AllTags/Tag/TagKey.tsx @@ -27,6 +27,7 @@ function TagsKey(props: TagsKeysProps): JSX.Element { start: globalTime.minTime, end: globalTime.maxTime, other: Object.fromEntries(traces.selectedFilter), + isFilterExclude: traces.isFilterExclude, }); if (response.statusCode === 200) { @@ -62,11 +63,11 @@ function TagsKey(props: TagsKeysProps): JSX.Element { const counter = useRef(0); useEffect(() => { - if (counter.current === 0) { + if (counter.current === 0 && selectedKey.length === 0) { counter.current = 1; onSearchHandler(); } - }, [onSearchHandler]); + }, [onSearchHandler, selectedKey.length]); return ( state.globalTime, ); - const valueSuggestion = useFetch(getTagValue, { - end: globalReducer.maxTime, - start: globalReducer.minTime, - tagKey, - }); + const { isLoading, data } = useQuery( + ['tagKey', globalReducer.minTime, globalReducer.maxTime, tagKey], + { + queryFn: () => + getTagValue({ + end: globalReducer.maxTime, + start: globalReducer.minTime, + tagKey, + }), + }, + ); return ( - {valueSuggestion.payload && - valueSuggestion.payload.map((suggestion) => ( + {data && + data.payload && + data.payload.map((suggestion) => ( {suggestion.tagValues} diff --git a/frontend/src/container/Trace/Search/index.tsx b/frontend/src/container/Trace/Search/index.tsx index 54bb51fa5a..890f69a6c4 100644 --- a/frontend/src/container/Trace/Search/index.tsx +++ b/frontend/src/container/Trace/Search/index.tsx @@ -96,6 +96,8 @@ function Search({ userSelected: traces.userSelectedFilter, isFilterExclude: traces.isFilterExclude, order: traces.spansAggregate.order, + pageSize: traces.spansAggregate.pageSize, + orderParam: traces.spansAggregate.orderParam, }, }); @@ -107,6 +109,8 @@ function Search({ traces.isFilterExclude, traces.userSelectedFilter, traces.spansAggregate.order, + traces.spansAggregate.pageSize, + traces.spansAggregate.orderParam, ); }; diff --git a/frontend/src/container/Trace/TraceTable/index.tsx b/frontend/src/container/Trace/TraceTable/index.tsx index b68a180ad7..84ff7d3021 100644 --- a/frontend/src/container/Trace/TraceTable/index.tsx +++ b/frontend/src/container/Trace/TraceTable/index.tsx @@ -4,6 +4,7 @@ import ROUTES from 'constants/routes'; import dayjs from 'dayjs'; import duration from 'dayjs/plugin/duration'; import history from 'lib/history'; +import omit from 'lodash-es/omit'; import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Dispatch } from 'redux'; @@ -12,7 +13,9 @@ import { AppState } from 'store/reducers'; import AppActions from 'types/actions'; import { UPDATE_SPAN_ORDER, + UPDATE_SPAN_ORDER_PARAMS, UPDATE_SPANS_AGGREGATE_PAGE_NUMBER, + UPDATE_SPANS_AGGREGATE_PAGE_SIZE, } from 'types/actions/trace'; import { TraceReducer } from 'types/reducer/trace'; @@ -25,14 +28,17 @@ function TraceTable(): JSX.Element { selectedTags, filterLoading, userSelectedFilter, - filter, isFilterExclude, filterToFetchData, + filter, } = useSelector((state) => state.traces); + const statusFilter = filter.get('status'); + const selectedStatusFilter = selectedFilter.get('status'); + const dispatch = useDispatch>(); - const { loading, total, order: spansAggregateOrder } = spansAggregate; + const { loading, order: spansAggregateOrder } = spansAggregate; type TableType = FlatArray; @@ -45,7 +51,7 @@ function TraceTable(): JSX.Element { }; const getHttpMethodOrStatus = ( - value: TableType['httpMethod'], + value: TableType['statusCode'], ): JSX.Element => { if (value.length === 0) { return -; @@ -80,6 +86,7 @@ function TraceTable(): JSX.Element { title: 'Duration', dataIndex: 'durationNano', key: 'durationNano', + sorter: true, render: (value: TableType['durationNano']): JSX.Element => ( {`${dayjs @@ -91,18 +98,28 @@ function TraceTable(): JSX.Element { }, { title: 'Method', - dataIndex: 'httpMethod', - key: 'httpMethod', + dataIndex: 'method', + key: 'method', render: getHttpMethodOrStatus, }, { title: 'Status Code', - dataIndex: 'httpCode', - key: 'httpCode', + dataIndex: 'statusCode', + key: 'statusCode', render: getHttpMethodOrStatus, }, ]; + const getSortKey = (key: string): string => { + if (key === 'durationNano') { + return 'duration'; + } + if (key === 'timestamp') { + return 'timestamp'; + } + return ''; + }; + const onChangeHandler: TableProps['onChange'] = ( props, _, @@ -111,7 +128,10 @@ function TraceTable(): JSX.Element { if (!Array.isArray(sort)) { const { order = spansAggregateOrder } = sort; if (props.current && props.pageSize) { - const spanOrder = order || spansAggregateOrder; + const spanOrder = order === 'ascend' ? 'ascending' : 'descending'; + const orderParam = getSortKey(sort.field as string); + + console.log({ spanOrder }); dispatch({ type: UPDATE_SPAN_ORDER, @@ -120,6 +140,20 @@ function TraceTable(): JSX.Element { }, }); + dispatch({ + type: UPDATE_SPAN_ORDER_PARAMS, + payload: { + orderParam, + }, + }); + + dispatch({ + type: UPDATE_SPANS_AGGREGATE_PAGE_SIZE, + payload: { + pageSize: props.pageSize, + }, + }); + dispatch({ type: UPDATE_SPANS_AGGREGATE_PAGE_NUMBER, payload: { @@ -135,11 +169,19 @@ function TraceTable(): JSX.Element { isFilterExclude, userSelectedFilter, spanOrder, + props.pageSize, + orderParam, ); } } }; + const totalObject = omit(statusFilter, [...(selectedStatusFilter || [])]); + const totalCount = Object.values(totalObject).reduce( + (a, b) => parseInt(String(a), 10) + parseInt(String(b), 10), + 0, + ) as number; + return ( ); } diff --git a/frontend/src/container/TraceDetail/SelectedSpanDetails/ErrorTag.tsx b/frontend/src/container/TraceDetail/SelectedSpanDetails/ErrorTag.tsx index d2c3cfb988..2a663387a5 100644 --- a/frontend/src/container/TraceDetail/SelectedSpanDetails/ErrorTag.tsx +++ b/frontend/src/container/TraceDetail/SelectedSpanDetails/ErrorTag.tsx @@ -1,12 +1,13 @@ import { Collapse, Modal } from 'antd'; +import Editor from 'components/Editor'; import { StyledButton } from 'components/Styled'; import useThemeMode from 'hooks/useThemeMode'; -import { keys, map } from 'lodash-es'; +import keys from 'lodash-es/keys'; +import map from 'lodash-es/map'; import React, { useState } from 'react'; import { ITraceTree } from 'types/api/trace/getTraceItem'; import { CustomSubText, CustomSubTitle, styles } from './styles'; -// import Editor from 'components/Editor'; const { Panel } = Collapse; @@ -70,17 +71,24 @@ function ErrorTag({ event }: ErrorTagProps): JSX.Element { ); })} + onToggleHandler(false)} title="Log Message" visible={isOpen} destroyOnClose footer={[]} + width="70vw" > {text.text} - - {text.subText} - + + {text.text === 'exception.stacktrace' ? ( + {}} readOnly value={text.subText} /> + ) : ( + + {text.subText} + + )} > ); diff --git a/frontend/src/container/TraceDetail/index.tsx b/frontend/src/container/TraceDetail/index.tsx index 489eaa4f26..c705123b50 100644 --- a/frontend/src/container/TraceDetail/index.tsx +++ b/frontend/src/container/TraceDetail/index.tsx @@ -110,7 +110,6 @@ function TraceDetail({ response }: TraceDetailProps): JSX.Element { selectedSpanId={activeSelectedId} onSpanHover={setActiveHoverId} onSpanSelect={setActiveSelectedId} - intervalUnit={intervalUnit} /> diff --git a/frontend/src/container/TraceDetail/utils.ts b/frontend/src/container/TraceDetail/utils.ts index 22cb741293..3c83607ce6 100644 --- a/frontend/src/container/TraceDetail/utils.ts +++ b/frontend/src/container/TraceDetail/utils.ts @@ -13,8 +13,9 @@ export const filterSpansByString = ( return JSON.stringify(spanWithoutChildren).includes(searchString); }); +type TTimeUnitName = 'ms' | 's' | 'm'; export interface IIntervalUnit { - name: 'ms' | 's' | 'm'; + name: TTimeUnitName; multiplier: number; } export const INTERVAL_UNITS: IIntervalUnit[] = [ @@ -39,6 +40,28 @@ export const resolveTimeFromInterval = ( return intervalTime * intervalUnit.multiplier; }; +export const convertTimeToRelevantUnit = ( + intervalTime: number, +): { time: number; timeUnitName: TTimeUnitName } => { + let relevantTime = { + time: intervalTime, + timeUnitName: INTERVAL_UNITS[0].name, + }; + + for (let idx = INTERVAL_UNITS.length - 1; idx >= 0; idx -= 1) { + const intervalUnit = INTERVAL_UNITS[idx]; + const convertedTimeForInterval = intervalTime * intervalUnit.multiplier; + if (convertedTimeForInterval >= 1) { + relevantTime = { + time: convertedTimeForInterval, + timeUnitName: intervalUnit.name, + }; + break; + } + } + return relevantTime; +}; + export const getSortedData = (treeData: ITraceTree): undefined | ITraceTree => { const traverse = (treeNode: ITraceTree, level = 0): void => { if (!treeNode) { diff --git a/frontend/src/container/TraceFlameGraph/index.tsx b/frontend/src/container/TraceFlameGraph/index.tsx index 08a998fe77..026efe58ca 100644 --- a/frontend/src/container/TraceFlameGraph/index.tsx +++ b/frontend/src/container/TraceFlameGraph/index.tsx @@ -1,14 +1,9 @@ /* eslint-disable react/no-unstable-nested-components */ import Color from 'color'; import { ITraceMetaData } from 'container/GantChart'; -import { - IIntervalUnit, - resolveTimeFromInterval, -} from 'container/TraceDetail/utils'; import useThemeMode from 'hooks/useThemeMode'; import React, { useLayoutEffect, useMemo, useState } from 'react'; import { ITraceTree } from 'types/api/trace/getTraceItem'; -import { toFixed } from 'utils/toFixed'; import { SpanItemContainer, @@ -98,14 +93,13 @@ function TraceFlameGraph(props: { onSpanSelect: SpanItemProps['onSpanSelect']; hoveredSpanId: string; selectedSpanId: string; - intervalUnit: IIntervalUnit; }): JSX.Element { const { treeData, traceMetaData, onSpanHover } = props; if (!treeData || treeData.id === 'empty' || !traceMetaData) { return ; } - const { intervalUnit, onSpanSelect, hoveredSpanId, selectedSpanId } = props; + const { onSpanSelect, hoveredSpanId, selectedSpanId } = props; const { globalStart, spread, levels } = traceMetaData; function RenderSpanRecursive({ @@ -131,10 +125,7 @@ function TraceFlameGraph(props: { const leftOffset = ((spanData.startTime - globalStart) * 100) / spread; const width = ((spanData.value / 1e6) * 100) / spread; - const toolTipText = `${spanData.name}\n${toFixed( - resolveTimeFromInterval(spanData.value / 1e6, intervalUnit), - 2, - )} ${intervalUnit.name}`; + const toolTipText = `${spanData.name}`; return ( <> diff --git a/frontend/src/container/TriggeredAlerts/NoFilterTable.tsx b/frontend/src/container/TriggeredAlerts/NoFilterTable.tsx index 9d5183e5e8..a9c8064616 100644 --- a/frontend/src/container/TriggeredAlerts/NoFilterTable.tsx +++ b/frontend/src/container/TriggeredAlerts/NoFilterTable.tsx @@ -31,7 +31,8 @@ function NoFilterTable({ dataIndex: 'labels', key: 'alertName', sorter: (a, b): number => - (a.labels?.alertname?.length || 0) - (b.labels?.alertname?.length || 0), + (a.labels?.alertname?.charCodeAt(0) || 0) - + (b.labels?.alertname?.charCodeAt(0) || 0), render: (data): JSX.Element => { const name = data?.alertname || ''; return {name}; diff --git a/frontend/src/container/TriggeredAlerts/index.tsx b/frontend/src/container/TriggeredAlerts/index.tsx index 88ebb2e138..a3762762df 100644 --- a/frontend/src/container/TriggeredAlerts/index.tsx +++ b/frontend/src/container/TriggeredAlerts/index.tsx @@ -2,7 +2,6 @@ import getTriggeredApi from 'api/alerts/getTriggered'; import Spinner from 'components/Spinner'; import { State } from 'hooks/useFetch'; import React, { useCallback, useEffect, useState } from 'react'; -import { Alerts } from 'types/api/alerts/getAll'; import { PayloadProps } from 'types/api/alerts/getTriggered'; import TriggerComponent from './TriggeredAlert'; diff --git a/frontend/src/hooks/useComponentPermission.ts b/frontend/src/hooks/useComponentPermission.ts new file mode 100644 index 0000000000..5294dd3f05 --- /dev/null +++ b/frontend/src/hooks/useComponentPermission.ts @@ -0,0 +1,22 @@ +import { useCallback, useMemo } from 'react'; +import { ROLES } from 'types/roles'; +import { componentPermission, ComponentTypes } from 'utils/permission'; + +const useComponentPermission = ( + component: ComponentTypes[], + role: ROLES | null, +): boolean[] => { + const getComponentPermission = useCallback( + (component: ComponentTypes): boolean => { + return !!componentPermission[component].find((roles) => role === roles); + }, + [role], + ); + + return useMemo(() => component.map((e) => getComponentPermission(e)), [ + component, + getComponentPermission, + ]); +}; + +export default useComponentPermission; diff --git a/frontend/src/index.html.ejs b/frontend/src/index.html.ejs index f3a7e7879a..c5e79d2e89 100644 --- a/frontend/src/index.html.ejs +++ b/frontend/src/index.html.ejs @@ -1,40 +1,82 @@ - - - Open source Observability platform | SigNoz - - - - - - - - - - + + + + + - - - -You need to enable JavaScript to run this app. - + + Open source Observability platform | SigNoz + + + + + + + + + + + - - + try { + userTheme = localStorage.getItem('theme'); + } catch (e) { + userTheme = ''; + } + + if (userTheme === 'lightMode') { + themeNode.setAttribute('href', '/css/antd.min.css'); + } else { + themeNode.setAttribute('href', '/css/antd.dark.min.css'); + } + + diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 0abe9a6453..99c7758c41 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -13,7 +13,13 @@ if (process.env.NODE_ENV === 'development') { reportWebVitals(console.log); } -const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + }, + }, +}); ReactDOM.render( diff --git a/frontend/src/lib/getChartData.ts b/frontend/src/lib/getChartData.ts index 4a57b07826..81b1c11d94 100644 --- a/frontend/src/lib/getChartData.ts +++ b/frontend/src/lib/getChartData.ts @@ -6,7 +6,7 @@ import convertIntoEpoc from './covertIntoEpoc'; import { colors } from './getRandomColor'; const getChartData = ({ queryData }: GetChartDataProps): ChartData => { - const response = queryData.data.map(({ query, queryData, legend }) => { + const response = queryData.map(({ query, queryData, legend }) => { return queryData.map((e) => { const { values = [], metric } = e || {}; const labelNames = getLabelName( @@ -19,7 +19,7 @@ const getChartData = ({ queryData }: GetChartDataProps): ChartData => { const [first = 0, second = ''] = e || []; return { first: new Date(parseInt(convertIntoEpoc(first * 1000), 10)), // converting in ms - second: Number(parseFloat(second).toFixed(2)), + second: Number(parseFloat(second)), }; }); @@ -59,7 +59,7 @@ const getChartData = ({ queryData }: GetChartDataProps): ChartData => { }; interface GetChartDataProps { - queryData: Widgets['queryData']; + queryData: Widgets['queryData']['data']; } export default getChartData; diff --git a/frontend/src/lib/getMinMax.ts b/frontend/src/lib/getMinMax.ts index ee369d9fba..9c1fab94c3 100644 --- a/frontend/src/lib/getMinMax.ts +++ b/frontend/src/lib/getMinMax.ts @@ -1,4 +1,4 @@ -import { Time } from 'container/Header/DateTimeSelection/config'; +import { Time } from 'container/TopNav/DateTimeSelection/config'; import { GlobalReducer } from 'types/reducer/globalTime'; import getMinAgo from './getStartAndEndTime/getMinAgo'; diff --git a/frontend/src/lib/resourceAttributes.ts b/frontend/src/lib/resourceAttributes.ts new file mode 100644 index 0000000000..5a1596efda --- /dev/null +++ b/frontend/src/lib/resourceAttributes.ts @@ -0,0 +1,71 @@ +import { OperatorConversions } from 'constants/resourceAttributes'; +import { IResourceAttributeQuery } from 'container/MetricsApplication/ResourceAttributesFilter/types'; +import { OperatorValues, Tags, TagsAPI } from 'types/reducer/trace'; + +/** + * resource_x_y -> x.y + */ +export const convertMetricKeyToTrace = (key: string): string => { + const splittedKey = key.split('_'); + + if (splittedKey.length <= 1) { + return ''; + } + return splittedKey.splice(1).join('.'); +}; + +/** + * x.y -> resource_x_y + */ +export const convertTraceKeyToMetric = (key: string): string => { + const splittedKey = key.split('.'); + return `resource_${splittedKey.join('_')}`; +}; + +export const convertOperatorLabelToMetricOperator = (label: string): string => { + return ( + OperatorConversions.find((operator) => operator.label === label) + ?.metricValue || '' + ); +}; + +export const convertOperatorLabelToTraceOperator = ( + label: string, +): OperatorValues => { + return OperatorConversions.find((operator) => operator.label === label) + ?.traceValue as OperatorValues; +}; + +export const convertRawQueriesToTraceSelectedTags = ( + queries: IResourceAttributeQuery[], + keyType: 'string' | 'array' = 'string', +): Tags[] | TagsAPI[] => { + return queries.map((query) => ({ + Key: + keyType === 'array' + ? [convertMetricKeyToTrace(query.tagKey)] + : (convertMetricKeyToTrace(query.tagKey) as never), + Operator: convertOperatorLabelToTraceOperator(query.operator), + Values: query.tagValue, + })); +}; + +/** + * Converts Resource Attribute Queries to PromQL query string + */ +export const resourceAttributesQueryToPromQL = ( + queries: IResourceAttributeQuery[], +): string => { + let parsedQueryString = ''; + + if (Array.isArray(queries)) + queries.forEach((query) => { + parsedQueryString += `, ${ + query.tagKey + }${convertOperatorLabelToMetricOperator( + query.operator, + )}"${query.tagValue.join('|')}"`; + }); + + return parsedQueryString; +}; diff --git a/frontend/src/pages/AllAlertChannels/index.tsx b/frontend/src/pages/AllAlertChannels/index.tsx deleted file mode 100644 index 1ab97d0db1..0000000000 --- a/frontend/src/pages/AllAlertChannels/index.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import RouteTab from 'components/RouteTab'; -import ROUTES from 'constants/routes'; -import AlertChannels from 'container/AllAlertChannels'; -import GeneralSettings from 'container/GeneralSettings'; -import history from 'lib/history'; -import React from 'react'; -import { useTranslation } from 'react-i18next'; - -function AllAlertChannels(): JSX.Element { - const pathName = history.location.pathname; - const { t } = useTranslation(); - return ( - - ); -} - -export default AllAlertChannels; diff --git a/frontend/src/pages/AllErrors/index.tsx b/frontend/src/pages/AllErrors/index.tsx new file mode 100644 index 0000000000..4093a20657 --- /dev/null +++ b/frontend/src/pages/AllErrors/index.tsx @@ -0,0 +1,26 @@ +import RouteTab from 'components/RouteTab'; +import ROUTES from 'constants/routes'; +import AllErrorsContainer from 'container/AllError'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +function AllErrors(): JSX.Element { + const { t } = useTranslation(); + + return ( + + ); +} + +export default AllErrors; diff --git a/frontend/src/pages/ChannelsEdit/index.tsx b/frontend/src/pages/ChannelsEdit/index.tsx index 4048eda81c..69dd1ee2ce 100644 --- a/frontend/src/pages/ChannelsEdit/index.tsx +++ b/frontend/src/pages/ChannelsEdit/index.tsx @@ -2,68 +2,99 @@ import { Typography } from 'antd'; import get from 'api/channels/get'; import Spinner from 'components/Spinner'; import { + PagerChannel, + PagerType, SlackChannel, SlackType, WebhookChannel, WebhookType, } from 'container/CreateAlertChannels/config'; import EditAlertChannels from 'container/EditAlertChannels'; -import useFetch from 'hooks/useFetch'; import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useQuery } from 'react-query'; import { useParams } from 'react-router-dom'; -import { PayloadProps, Props } from 'types/api/channels/get'; function ChannelsEdit(): JSX.Element { const { id } = useParams(); + const { t } = useTranslation(); - const { errorMessage, payload, error, loading } = useFetch< - PayloadProps, - Props - >(get, { - id, + const { isLoading, isError, data } = useQuery(['getChannel', id], { + queryFn: () => + get({ + id, + }), }); - if (error) { - return {errorMessage}; + if (isError) { + return {data?.error || t('something_went_wrong')}; } - if (loading || payload === undefined) { + if (isLoading || !data?.payload) { return ; } - const { data } = payload; + const { data: ChannelData } = data.payload; - const value = JSON.parse(data); - let type = ''; - let channel: SlackChannel & WebhookChannel = { name: '' }; + const value = JSON.parse(ChannelData); - if (value && 'slack_configs' in value) { - const slackConfig = value.slack_configs[0]; - channel = slackConfig; - type = SlackType; - } else if (value && 'webhook_configs' in value) { - const webhookConfig = value.webhook_configs[0]; - channel = webhookConfig; - channel.api_url = webhookConfig.url; - - if ('http_config' in webhookConfig) { - const httpConfig = webhookConfig.http_config; - if ('basic_auth' in httpConfig) { - channel.username = webhookConfig.http_config?.basic_auth?.username; - channel.password = webhookConfig.http_config?.basic_auth?.password; - } else if ('authorization' in httpConfig) { - channel.password = webhookConfig.http_config?.authorization?.credentials; - } + const prepChannelConfig = (): { + type: string; + channel: SlackChannel & WebhookChannel & PagerChannel; + } => { + let channel: SlackChannel & WebhookChannel & PagerChannel = { name: '' }; + if (value && 'slack_configs' in value) { + const slackConfig = value.slack_configs[0]; + channel = slackConfig; + return { + type: SlackType, + channel, + }; } - type = WebhookType; - } - console.log('channel:', channel); + if (value && 'pagerduty_configs' in value) { + const pagerConfig = value.pagerduty_configs[0]; + channel = pagerConfig; + channel.details = JSON.stringify(pagerConfig.details); + channel.detailsArray = { ...pagerConfig.details }; + return { + type: PagerType, + channel, + }; + } + + if (value && 'webhook_configs' in value) { + const webhookConfig = value.webhook_configs[0]; + channel = webhookConfig; + channel.api_url = webhookConfig.url; + + if ('http_config' in webhookConfig) { + const httpConfig = webhookConfig.http_config; + if ('basic_auth' in httpConfig) { + channel.username = webhookConfig.http_config?.basic_auth?.username; + channel.password = webhookConfig.http_config?.basic_auth?.password; + } else if ('authorization' in httpConfig) { + channel.password = webhookConfig.http_config?.authorization?.credentials; + } + } + return { + type: WebhookType, + channel, + }; + } + return { + type: SlackType, + channel, + }; + }; + + const target = prepChannelConfig(); + return ( ( + const [value, setEditorValue] = useState( `\n alert: High RPS\n expr: sum(rate(signoz_latency_count{span_kind="SPAN_KIND_SERVER"}[2m])) by (service_name) > 100\n for: 0m\n labels:\n severity: warning\n annotations:\n summary: High RPS of Applications\n description: "RPS is > 100\n\t\t\t VALUE = {{ $value }}\n\t\t\t LABELS = {{ $labels }}"\n `, ); @@ -36,7 +36,7 @@ function CreateAlert(): JSX.Element { loading: true, })); - if (value.current.length === 0) { + if (value.length === 0) { setNewAlertState((state) => ({ ...state, loading: false, @@ -49,7 +49,7 @@ function CreateAlert(): JSX.Element { } const response = await createAlertsApi({ - query: value.current, + query: value, }); if (response.statusCode === 200) { @@ -83,14 +83,14 @@ function CreateAlert(): JSX.Element { message: defaultError, }); } - }, [notifications]); + }, [notifications, value]); return ( <> {Element} Create New Alert - + setEditorValue(value)} value={value} /> (); + const { t } = useTranslation('common'); - const { loading, error, payload, errorMessage } = useFetch< - PayloadProps, - Props - >(get, { - id: parseInt(ruleId, 10), + const { isLoading, data, isError } = useQuery(['ruleId', ruleId], { + queryFn: () => + get({ + id: parseInt(ruleId, 10), + }), }); - if (error) { - return {errorMessage}; + if (isError) { + return {data?.error || t('something_went_wrong')}; } - if (loading || payload === undefined) { + if (isLoading || !data?.payload) { return ; } - return ; + return ; } interface EditRulesParam { diff --git a/frontend/src/pages/ErrorDetails/index.tsx b/frontend/src/pages/ErrorDetails/index.tsx new file mode 100644 index 0000000000..ddc677c7b5 --- /dev/null +++ b/frontend/src/pages/ErrorDetails/index.tsx @@ -0,0 +1,97 @@ +import { Typography } from 'antd'; +import getByErrorType from 'api/errors/getByErrorTypeAndService'; +import getById from 'api/errors/getById'; +import Spinner from 'components/Spinner'; +import ROUTES from 'constants/routes'; +import ErrorDetailsContainer from 'container/ErrorDetails'; +import React from 'react'; +import { useQuery } from 'react-query'; +import { useSelector } from 'react-redux'; +import { Redirect, useLocation } from 'react-router-dom'; +import { AppState } from 'store/reducers'; +import { PayloadProps } from 'types/api/errors/getById'; +import { GlobalReducer } from 'types/reducer/globalTime'; + +function ErrorDetails(): JSX.Element { + const { maxTime, minTime } = useSelector( + (state) => state.globalTime, + ); + const { search } = useLocation(); + const params = new URLSearchParams(search); + + const errorId = params.get('errorId'); + const errorType = params.get('errorType'); + const serviceName = params.get('serviceName'); + + const { data, status } = useQuery( + [ + 'errorByType', + errorType, + 'serviceName', + serviceName, + maxTime, + minTime, + errorId, + ], + { + queryFn: () => + getByErrorType({ + end: maxTime, + errorType: errorType || '', + serviceName: serviceName || '', + start: minTime, + }), + enabled: errorId === null && errorType !== null && serviceName !== null, + cacheTime: 5000, + }, + ); + + const { status: ErrorIdStatus, data: errorIdPayload } = useQuery( + [ + 'errorByType', + errorType, + 'serviceName', + serviceName, + maxTime, + minTime, + 'errorId', + errorId, + ], + { + queryFn: () => + getById({ + end: maxTime, + errorId: errorId || data?.payload?.errorId || '', + start: minTime, + }), + enabled: + (errorId !== null || status === 'success') && + errorType !== null && + serviceName !== null, + cacheTime: 5000, + }, + ); + + if (errorType === null || serviceName === null) { + return ; + } + + if (status === 'loading' || ErrorIdStatus === 'loading') { + return ; + } + + if (status === 'error' || ErrorIdStatus === 'error') { + return {data?.error || errorIdPayload?.error}; + } + + return ( + + ); +} + +export interface ErrorDetailsParams { + errorType: string; + serviceName: string; +} + +export default ErrorDetails; diff --git a/frontend/src/pages/Login/index.tsx b/frontend/src/pages/Login/index.tsx new file mode 100644 index 0000000000..5a1adc4b99 --- /dev/null +++ b/frontend/src/pages/Login/index.tsx @@ -0,0 +1,47 @@ +import { Typography } from 'antd'; +import getUserVersion from 'api/user/getVersion'; +import Spinner from 'components/Spinner'; +import WelcomeLeftContainer from 'components/WelcomeLeftContainer'; +import LoginContainer from 'container/Login'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useQuery } from 'react-query'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import AppReducer from 'types/reducer/app'; + +function Login(): JSX.Element { + const { isLoggedIn } = useSelector((state) => state.app); + const { t } = useTranslation(); + + const versionResult = useQuery({ + queryFn: getUserVersion, + queryKey: 'getUserVersion', + enabled: !isLoggedIn, + }); + + if (versionResult.status === 'error') { + return ( + + {versionResult.data?.error || t('something_went_wrong')} + + ); + } + + if ( + versionResult.status === 'loading' || + !(versionResult.data && versionResult.data.payload) + ) { + return ; + } + + const { version } = versionResult.data.payload; + + return ( + + + + ); +} + +export default Login; diff --git a/frontend/src/pages/MetricApplication/index.tsx b/frontend/src/pages/MetricApplication/index.tsx index db521f3dd9..96e12f50ea 100644 --- a/frontend/src/pages/MetricApplication/index.tsx +++ b/frontend/src/pages/MetricApplication/index.tsx @@ -1,7 +1,8 @@ import { Typography } from 'antd'; import Spinner from 'components/Spinner'; import MetricsApplicationContainer from 'container/MetricsApplication'; -import React, { useEffect } from 'react'; +import { convertRawQueriesToTraceSelectedTags } from 'lib/resourceAttributes'; +import React, { useEffect, useMemo } from 'react'; import { connect, useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; import { bindActionCreators } from 'redux'; @@ -10,16 +11,13 @@ import { GetInitialData, GetInitialDataProps, } from 'store/actions/metrics/getInitialData'; -import { ResetInitialData } from 'store/actions/metrics/resetInitialData'; import { AppState } from 'store/reducers'; import AppActions from 'types/actions'; import { GlobalReducer } from 'types/reducer/globalTime'; import MetricReducer from 'types/reducer/metrics'; +import { Tags } from 'types/reducer/trace'; -function MetricsApplication({ - getInitialData, - resetInitialData, -}: MetricsProps): JSX.Element { +function MetricsApplication({ getInitialData }: MetricsProps): JSX.Element { const { minTime, maxTime } = useSelector( (state) => state.globalTime, ); @@ -30,19 +28,27 @@ function MetricsApplication({ const { servicename } = useParams(); + const { resourceAttributeQueries } = useSelector( + (state) => state.metrics, + ); + + const selectedTags = useMemo( + () => + (convertRawQueriesToTraceSelectedTags(resourceAttributeQueries) as Tags[]) || + [], + [resourceAttributeQueries], + ); + useEffect(() => { if (servicename !== undefined) { getInitialData({ serviceName: servicename, maxTime, minTime, + selectedTags, }); } - - return (): void => { - resetInitialData(); - }; - }, [servicename, getInitialData, resetInitialData, maxTime, minTime]); + }, [servicename, getInitialData, maxTime, minTime, selectedTags]); if (metricsApplicationLoading) { return ; @@ -57,7 +63,6 @@ function MetricsApplication({ interface DispatchProps { getInitialData: (props: GetInitialDataProps) => void; - resetInitialData: () => void; } interface ServiceProps { @@ -68,7 +73,6 @@ const mapDispatchToProps = ( dispatch: ThunkDispatch, ): DispatchProps => ({ getInitialData: bindActionCreators(GetInitialData, dispatch), - resetInitialData: bindActionCreators(ResetInitialData, dispatch), }); type MetricsProps = DispatchProps; diff --git a/frontend/src/pages/Metrics/index.tsx b/frontend/src/pages/Metrics/index.tsx index 8380fb7219..879414ec4b 100644 --- a/frontend/src/pages/Metrics/index.tsx +++ b/frontend/src/pages/Metrics/index.tsx @@ -1,8 +1,10 @@ import getLocalStorageKey from 'api/browser/localstorage/get'; import Spinner from 'components/Spinner'; import { SKIP_ONBOARDING } from 'constants/onboarding'; +import ResourceAttributesFilter from 'container/MetricsApplication/ResourceAttributesFilter'; import MetricTable from 'container/MetricsTable'; -import React, { useEffect } from 'react'; +import { convertRawQueriesToTraceSelectedTags } from 'lib/resourceAttributes'; +import React, { useEffect, useMemo } from 'react'; import { connect, useSelector } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; import { ThunkDispatch } from 'redux-thunk'; @@ -11,16 +13,24 @@ import { AppState } from 'store/reducers'; import AppActions from 'types/actions'; import { GlobalReducer } from 'types/reducer/globalTime'; import MetricReducer from 'types/reducer/metrics'; +import { Tags } from 'types/reducer/trace'; function Metrics({ getService }: MetricsProps): JSX.Element { const { minTime, maxTime, loading, selectedTime } = useSelector< AppState, GlobalReducer >((state) => state.globalTime); - const { services } = useSelector( - (state) => state.metrics, - ); + const { services, resourceAttributeQueries } = useSelector< + AppState, + MetricReducer + >((state) => state.metrics); + const selectedTags = useMemo( + () => + (convertRawQueriesToTraceSelectedTags(resourceAttributeQueries) as Tags[]) || + [], + [resourceAttributeQueries], + ); const isSkipped = getLocalStorageKey(SKIP_ONBOARDING) === 'true'; useEffect(() => { @@ -28,9 +38,10 @@ function Metrics({ getService }: MetricsProps): JSX.Element { getService({ maxTime, minTime, + selectedTags, }); } - }, [getService, loading, maxTime, minTime]); + }, [getService, loading, maxTime, minTime, selectedTags]); useEffect(() => { let timeInterval: NodeJS.Timeout; @@ -40,6 +51,7 @@ function Metrics({ getService }: MetricsProps): JSX.Element { getService({ maxTime, minTime, + selectedTags, }); }, 50000); } @@ -47,13 +59,27 @@ function Metrics({ getService }: MetricsProps): JSX.Element { return (): void => { clearInterval(timeInterval); }; - }, [getService, isSkipped, loading, maxTime, minTime, services, selectedTime]); + }, [ + getService, + isSkipped, + loading, + maxTime, + minTime, + services, + selectedTime, + selectedTags, + ]); if (loading) { return ; } - return ; + return ( + <> + + + > + ); } interface DispatchProps { diff --git a/frontend/src/pages/MySettings/index.tsx b/frontend/src/pages/MySettings/index.tsx new file mode 100644 index 0000000000..7029560078 --- /dev/null +++ b/frontend/src/pages/MySettings/index.tsx @@ -0,0 +1,7 @@ +import MySettingsContainer from 'container/MySettings'; +import React from 'react'; + +function MySettings(): JSX.Element { + return ; +} +export default MySettings; diff --git a/frontend/src/pages/ResetPassword/index.tsx b/frontend/src/pages/ResetPassword/index.tsx new file mode 100644 index 0000000000..f45e7d15f3 --- /dev/null +++ b/frontend/src/pages/ResetPassword/index.tsx @@ -0,0 +1,48 @@ +import { Typography } from 'antd'; +import getUserVersion from 'api/user/getVersion'; +import Spinner from 'components/Spinner'; +import ResetPasswordContainer from 'container/ResetPassword'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useQueries } from 'react-query'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import AppReducer from 'types/reducer/app'; + +function ResetPassword(): JSX.Element { + const { t } = useTranslation('common'); + const { isLoggedIn } = useSelector((state) => state.app); + + const [versionResponse] = useQueries([ + { + queryFn: getUserVersion, + queryKey: 'getUserVersion', + enabled: !isLoggedIn, + }, + ]); + + if ( + versionResponse.status === 'error' || + (versionResponse.status === 'success' && + versionResponse.data?.statusCode !== 200) + ) { + return ( + + {versionResponse.data?.error || t('something_went_wrong')} + + ); + } + + if ( + versionResponse.status === 'loading' || + !(versionResponse.data && versionResponse.data.payload) + ) { + return ; + } + + const { version } = versionResponse.data.payload; + + return ; +} + +export default ResetPassword; diff --git a/frontend/src/pages/Settings/index.tsx b/frontend/src/pages/Settings/index.tsx index 208fcde7c1..20b76122aa 100644 --- a/frontend/src/pages/Settings/index.tsx +++ b/frontend/src/pages/Settings/index.tsx @@ -2,28 +2,60 @@ import RouteTab from 'components/RouteTab'; import ROUTES from 'constants/routes'; import AlertChannels from 'container/AllAlertChannels'; import GeneralSettings from 'container/GeneralSettings'; +import OrganizationSettings from 'container/OrganizationSettings'; +import useComponentPermission from 'hooks/useComponentPermission'; import history from 'lib/history'; import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import AppReducer from 'types/reducer/app'; function SettingsPage(): JSX.Element { const pathName = history.location.pathname; + const { t } = useTranslation(['routes']); + const { role } = useSelector((state) => state.app); + const [currentOrgSettings] = useComponentPermission( + ['current_org_settings'], + role, + ); + + const getActiveKey = (pathname: string): string => { + if (pathname === ROUTES.SETTINGS) { + return t('general'); + } + if (pathname === ROUTES.ORG_SETTINGS && currentOrgSettings) { + return t('organization_settings'); + } + return t('alert_channels'); + }; + + const common = [ + { + Component: GeneralSettings, + name: t('general'), + route: ROUTES.SETTINGS, + }, + { + Component: AlertChannels, + name: t('alert_channels'), + route: ROUTES.ALL_CHANNELS, + }, + ]; + + if (currentOrgSettings) { + common.push({ + Component: OrganizationSettings, + name: t('organization_settings'), + route: ROUTES.ORG_SETTINGS, + }); + } return ( ); diff --git a/frontend/src/pages/SignUp/SignUp.tsx b/frontend/src/pages/SignUp/SignUp.tsx index 88f92660f1..1a99e0a5a5 100644 --- a/frontend/src/pages/SignUp/SignUp.tsx +++ b/frontend/src/pages/SignUp/SignUp.tsx @@ -1,55 +1,65 @@ -import { - Button, - Card, - Input, - notification, - Space, - Switch, - Typography, -} from 'antd'; -import setLocalStorageKey from 'api/browser/localstorage/set'; -import setPreference from 'api/user/setPreference'; -import signup from 'api/user/signup'; -import { IS_LOGGED_IN } from 'constants/auth'; +import { Button, Input, notification, Space, Switch, Typography } from 'antd'; +import editOrg from 'api/user/editOrg'; +import getInviteDetails from 'api/user/getInviteDetails'; +import loginApi from 'api/user/login'; +import signUpApi from 'api/user/signup'; +import afterLogin from 'AppRoutes/utils'; +import WelcomeLeftContainer from 'components/WelcomeLeftContainer'; import ROUTES from 'constants/routes'; import history from 'lib/history'; import React, { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useDispatch } from 'react-redux'; -import { Dispatch } from 'redux'; -import AppActions from 'types/actions'; -import { PayloadProps } from 'types/api/user/getUserPreference'; +import { useQuery } from 'react-query'; +import { useLocation } from 'react-router-dom'; +import { SuccessResponse } from 'types/api'; +import { PayloadProps } from 'types/api/user/getUser'; -import { - ButtonContainer, - Container, - FormWrapper, - Label, - LeftContainer, - Logo, - MarginTop, -} from './styles'; +import { ButtonContainer, FormWrapper, Label, MarginTop } from './styles'; +import { isPasswordNotValidMessage, isPasswordValid } from './utils'; const { Title } = Typography; -function Signup({ version, userpref }: SignupProps): JSX.Element { +function SignUp({ version }: SignUpProps): JSX.Element { const [loading, setLoading] = useState(false); - const { t } = useTranslation(); const [firstName, setFirstName] = useState(''); const [email, setEmail] = useState(''); - const [organizationName, setOrganisationName] = useState(''); - const [hasOptedUpdates, setHasOptedUpdates] = useState( - userpref.hasOptedUpdates, + const [organizationName, setOrganizationName] = useState(''); + const [hasOptedUpdates, setHasOptedUpdates] = useState(true); + const [isAnonymous, setIsAnonymous] = useState(false); + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [confirmPasswordError, setConfirmPasswordError] = useState( + false, ); - const [isAnonymous, setisAnonymous] = useState(userpref.isAnonymous); + const [isPasswordPolicyError, setIsPasswordPolicyError] = useState( + false, + ); + const { search } = useLocation(); + const params = new URLSearchParams(search); + const token = params.get('token'); + const [isDetailsDisable, setIsDetailsDisable] = useState(false); - const dispatch = useDispatch>(); + const getInviteDetailsResponse = useQuery({ + queryFn: () => + getInviteDetails({ + inviteId: token || '', + }), + queryKey: 'getInviteDetails', + enabled: token !== null, + }); useEffect(() => { - setisAnonymous(userpref.isAnonymous); - setHasOptedUpdates(userpref.hasOptedUpdates); - }, [userpref]); + if ( + getInviteDetailsResponse.status === 'success' && + getInviteDetailsResponse.data.payload + ) { + const responseDetails = getInviteDetailsResponse.data.payload; + setFirstName(responseDetails.name); + setEmail(responseDetails.email); + setOrganizationName(responseDetails.organization); + setIsDetailsDisable(true); + } + }, [getInviteDetailsResponse?.data?.payload, getInviteDetailsResponse.status]); const setState = ( value: string, @@ -59,6 +69,70 @@ function Signup({ version, userpref }: SignupProps): JSX.Element { }; const defaultError = 'Something went wrong'; + const isPreferenceVisible = token === null; + + const commonHandler = async ( + callback: (e: SuccessResponse) => Promise | VoidFunction, + ): Promise => { + try { + const response = await signUpApi({ + email, + name: firstName, + orgName: organizationName, + password, + token: params.get('token') || undefined, + }); + + if (response.statusCode === 200) { + const loginResponse = await loginApi({ + email, + password, + }); + + if (loginResponse.statusCode === 200) { + const { payload } = loginResponse; + const userResponse = await afterLogin( + payload.userId, + payload.accessJwt, + payload.refreshJwt, + ); + if (userResponse) { + callback(userResponse); + } + } else { + notification.error({ + message: loginResponse.error || defaultError, + }); + } + } else { + notification.error({ + message: response.error || defaultError, + }); + } + } catch (error) { + notification.error({ + message: defaultError, + }); + } + }; + + const onAdminAfterLogin = async ( + userResponse: SuccessResponse, + ): Promise => { + const editResponse = await editOrg({ + isAnonymous, + name: organizationName, + hasOptedUpdates, + orgId: userResponse.payload.orgId, + }); + if (editResponse.statusCode === 200) { + history.push(ROUTES.APPLICATION); + } else { + notification.error({ + message: editResponse.error || defaultError, + }); + } + }; const handleSubmit = (e: React.FormEvent): void => { (async (): Promise => { @@ -66,39 +140,23 @@ function Signup({ version, userpref }: SignupProps): JSX.Element { e.preventDefault(); setLoading(true); - const userPrefernceResponse = await setPreference({ - isAnonymous, - hasOptedUpdates, - }); - - if (userPrefernceResponse.statusCode === 200) { - const response = await signup({ - email, - name: firstName, - organizationName, - }); - - if (response.statusCode === 200) { - setLocalStorageKey(IS_LOGGED_IN, 'yes'); - dispatch({ - type: 'LOGGED_IN', - }); - - history.push(ROUTES.APPLICATION); - } else { - setLoading(false); - - notification.error({ - message: defaultError, - }); - } - } else { + if (!isPasswordValid(password)) { + setIsPasswordPolicyError(true); setLoading(false); - - notification.error({ - message: defaultError, - }); + return; } + + if (isPreferenceVisible) { + await commonHandler(onAdminAfterLogin); + } else { + await commonHandler( + async (): Promise => { + history.push(ROUTES.APPLICATION); + }, + ); + } + + setLoading(false); } catch (error) { notification.error({ message: defaultError, @@ -108,8 +166,6 @@ function Signup({ version, userpref }: SignupProps): JSX.Element { })(); }; - console.log(userpref); - const onSwitchHandler = ( value: boolean, setFunction: React.Dispatch>, @@ -117,29 +173,34 @@ function Signup({ version, userpref }: SignupProps): JSX.Element { setFunction(value); }; - return ( - - - - - SigNoz - - {t('monitor_signup')} - - SigNoz {version} - - + const getIsNameVisible = (): boolean => + !(firstName.length === 0 && !isPreferenceVisible); + const isNameVisible = getIsNameVisible(); + + useEffect(() => { + if (!isPasswordValid(password) && password.length) { + setIsPasswordPolicyError(true); + } else { + setIsPasswordPolicyError(false); + } + + if (password !== confirmPassword) { + setConfirmPasswordError(true); + } else { + setConfirmPasswordError(false); + } + }, [password, confirmPassword]); + + return ( + Create your account Email - - First Name - { - setState(e.target.value, setFirstName); - }} - required - id="signupFirstName" - /> - + {isNameVisible && ( + + First Name + { + setState(e.target.value, setFirstName); + }} + required + id="signupFirstName" + disabled={isDetailsDisable} + /> + + )} + Organization Name { - setState(e.target.value, setOrganisationName); + setState(e.target.value, setOrganizationName); }} required id="organizationName" + disabled={isDetailsDisable} /> + + Password + { + setState(e.target.value, setPassword); + }} + required + id="currentPassword" + /> + + + Confirm Password + { + const updateValue = e.target.value; + setState(updateValue, setConfirmPassword); + }} + required + id="UpdatePassword" + /> - - - onSwitchHandler(value, setHasOptedUpdates)} - checked={hasOptedUpdates} - /> - Keep me updated on new SigNoz features - - + {confirmPasswordError && ( + + Passwords don’t match. Please try again + + )} + {isPasswordPolicyError && ( + + {isPasswordNotValidMessage} + + )} + - - - onSwitchHandler(value, setisAnonymous)} - checked={isAnonymous} - /> - - Anonymise my usage date. We collect data to measure product usage - - - + {isPreferenceVisible && ( + <> + + + onSwitchHandler(value, setHasOptedUpdates)} + checked={hasOptedUpdates} + /> + Keep me updated on new SigNoz features + + + + + + onSwitchHandler(value, setIsAnonymous)} + checked={isAnonymous} + /> + + Anonymise my usage date. We collect data to measure product usage + + + + > + )} + + {isPreferenceVisible && ( + + This will create an admin account. If you are not an admin, please ask + your admin for an invite link + + )} Get Started - + ); } -interface SignupProps { +interface SignUpProps { version: string; - userpref: PayloadProps; } -export default Signup; +export default SignUp; diff --git a/frontend/src/pages/SignUp/index.tsx b/frontend/src/pages/SignUp/index.tsx index 1bf16285a4..2a073a756a 100644 --- a/frontend/src/pages/SignUp/index.tsx +++ b/frontend/src/pages/SignUp/index.tsx @@ -1,43 +1,49 @@ import { Typography } from 'antd'; -import getPreference from 'api/user/getPreference'; -import getVersion from 'api/user/getVersion'; +import getUserVersion from 'api/user/getVersion'; import Spinner from 'components/Spinner'; -import useFetch from 'hooks/useFetch'; import React from 'react'; -import { PayloadProps as UserPrefPayload } from 'types/api/user/getUserPreference'; -import { PayloadProps as VersionPayload } from 'types/api/user/getVersion'; +import { useTranslation } from 'react-i18next'; +import { useQueries } from 'react-query'; +import { useSelector } from 'react-redux'; +import { AppState } from 'store/reducers'; +import AppReducer from 'types/reducer/app'; import SignUpComponent from './SignUp'; function SignUp(): JSX.Element { - const versionResponse = useFetch(getVersion); + const { t } = useTranslation('common'); + const { isLoggedIn } = useSelector((state) => state.app); - const userPrefResponse = useFetch(getPreference); + const [versionResponse] = useQueries([ + { + queryFn: getUserVersion, + queryKey: 'getUserVersion', + enabled: !isLoggedIn, + }, + ]); - if (versionResponse.error || userPrefResponse.error) { + if ( + versionResponse.status === 'error' || + (versionResponse.status === 'success' && + versionResponse.data?.statusCode !== 200) + ) { return ( - {versionResponse.errorMessage || - userPrefResponse.errorMessage || - 'Somehthing went wrong'} + {versionResponse.data?.error || t('something_went_wrong')} ); } if ( - versionResponse.loading || - versionResponse.payload === undefined || - userPrefResponse.loading || - userPrefResponse.payload === undefined + versionResponse.status === 'loading' || + !(versionResponse.data && versionResponse.data.payload) ) { - return ; + return ; } - const { version } = versionResponse.payload; + const { version } = versionResponse.data.payload; - const userpref = userPrefResponse.payload; - - return ; + return ; } export default SignUp; diff --git a/frontend/src/pages/SignUp/styles.ts b/frontend/src/pages/SignUp/styles.ts index 3bdfc77389..3e6064f6f7 100644 --- a/frontend/src/pages/SignUp/styles.ts +++ b/frontend/src/pages/SignUp/styles.ts @@ -1,19 +1,7 @@ -import { Card, Space } from 'antd'; +import { Card } from 'antd'; import React from 'react'; import styled from 'styled-components'; -export const Container = styled.div` - &&& { - display: flex; - justify-content: center; - align-items: center; - min-height: 100vh; - - max-width: 1024px; - margin: 0 auto; - } -`; - export const FormWrapper = styled(Card)` display: flex; justify-content: center; @@ -29,10 +17,6 @@ export const Label = styled.label` line-height: 24px; `; -export const LeftContainer = styled(Space)` - flex: 1; -`; - export const ButtonContainer = styled.div` margin-top: 1.8125rem; display: flex; @@ -47,7 +31,3 @@ interface Props { export const MarginTop = styled.div` margin-top: ${({ marginTop = 0 }): number | string => marginTop}; `; - -export const Logo = styled.img` - width: 60px; -`; diff --git a/frontend/src/pages/SignUp/utils.ts b/frontend/src/pages/SignUp/utils.ts new file mode 100644 index 0000000000..a71c4029cf --- /dev/null +++ b/frontend/src/pages/SignUp/utils.ts @@ -0,0 +1,15 @@ +/** + * @function + * @description to check whether password is valid or not + * @reference stackoverflow.com/a/69807687 + * @returns Boolean + */ +export const isPasswordValid = (value: string): boolean => { + // eslint-disable-next-line prefer-regex-literals + const pattern = new RegExp( + '^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$', + ); + return pattern.test(value); +}; + +export const isPasswordNotValidMessage = `Password must a have minimum of 8 characters with at least one lower case, one number ,one upper case and one special character`; diff --git a/frontend/src/pages/SomethingWentWrong/index.tsx b/frontend/src/pages/SomethingWentWrong/index.tsx new file mode 100644 index 0000000000..14619a841e --- /dev/null +++ b/frontend/src/pages/SomethingWentWrong/index.tsx @@ -0,0 +1,25 @@ +import { Button, Typography } from 'antd'; +import SomethingWentWrongAsset from 'assets/SomethingWentWrong'; +import { Container } from 'components/NotFound/styles'; +import ROUTES from 'constants/routes'; +import history from 'lib/history'; +import React from 'react'; + +function SomethingWentWrong(): JSX.Element { + return ( + + + Oops! Something went wrong + { + history.push(ROUTES.APPLICATION); + }} + > + Return to Metrics page + + + ); +} + +export default SomethingWentWrong; diff --git a/frontend/src/pages/Trace/index.tsx b/frontend/src/pages/Trace/index.tsx index 9764c83afe..ed3d234436 100644 --- a/frontend/src/pages/Trace/index.tsx +++ b/frontend/src/pages/Trace/index.tsx @@ -64,7 +64,8 @@ function Trace({ current: spansAggregate.currentPage, pageSize: spansAggregate.pageSize, selectedTags, - order: spansAggregate.order === 'ascend' ? 'ascending' : 'descending', + order: spansAggregate.order, + orderParam: spansAggregate.orderParam, }); }, [ selectedTags, @@ -75,6 +76,7 @@ function Trace({ spansAggregate.currentPage, spansAggregate.pageSize, spansAggregate.order, + spansAggregate.orderParam, ]); useEffect(() => { diff --git a/frontend/src/pages/UnAuthorized/index.tsx b/frontend/src/pages/UnAuthorized/index.tsx new file mode 100644 index 0000000000..d856e26477 --- /dev/null +++ b/frontend/src/pages/UnAuthorized/index.tsx @@ -0,0 +1,23 @@ +import { Space, Typography } from 'antd'; +import UnAuthorized from 'assets/UnAuthorized'; +import { Button, Container } from 'components/NotFound/styles'; +import ROUTES from 'constants/routes'; +import React from 'react'; + +function UnAuthorizePage(): JSX.Element { + return ( + + + + + Oops.. you don't have permission to view this page + + + Return To Metrics Page + + + + ); +} + +export default UnAuthorizePage; diff --git a/frontend/src/store/actions/dashboard/deleteWidget.ts b/frontend/src/store/actions/dashboard/deleteWidget.ts index d86ba2b086..6e2a34cf93 100644 --- a/frontend/src/store/actions/dashboard/deleteWidget.ts +++ b/frontend/src/store/actions/dashboard/deleteWidget.ts @@ -17,12 +17,14 @@ export const DeleteWidget = ({ const updatedWidgets = widgets.filter((e) => e.id !== widgetId); const response = await updateDashboardApi({ - title: selectedDashboard.data.title, + data: { + title: selectedDashboard.data.title, + description: selectedDashboard.data.description, + name: selectedDashboard.data.name, + tags: selectedDashboard.data.tags, + widgets: updatedWidgets, + }, uuid: selectedDashboard.uuid, - description: selectedDashboard.data.description, - name: selectedDashboard.data.name, - tags: selectedDashboard.data.tags, - widgets: updatedWidgets, }); if (response.statusCode === 200) { diff --git a/frontend/src/store/actions/dashboard/saveDashboard.ts b/frontend/src/store/actions/dashboard/saveDashboard.ts index e14e40b737..32ca92ab8b 100644 --- a/frontend/src/store/actions/dashboard/saveDashboard.ts +++ b/frontend/src/store/actions/dashboard/saveDashboard.ts @@ -57,38 +57,40 @@ export const SaveDashboard = ({ ]; const response = await updateDashboardApi({ - ...selectedDashboard.data, - uuid, - // this is the data for the dashboard - title: selectedDashboard.data.title, - description: selectedDashboard.data.description, - tags: selectedDashboard.data.tags, - name: selectedDashboard.data.name, - // as we are updated the widget only - widgets: [ - ...preWidget, - { - ...selectedWidget, - description: updatedDescription, - id: widgetId, - isStacked: updatedisStacked, - nullZeroValues: updatednullZeroValues, - opacity: updatedopacity, - title: updatedTitle, - timePreferance: updatedtimePreferance, - yAxisUnit: updatedYAxisUnit, - queryData: { - ...selectedWidget.queryData, - data: [ - ...selectedWidget.queryData.data.map((e) => ({ - ...e, - queryData: [], - })), - ], + data: { + ...selectedDashboard.data, + // this is the data for the dashboard + title: selectedDashboard.data.title, + description: selectedDashboard.data.description, + tags: selectedDashboard.data.tags, + name: selectedDashboard.data.name, + // as we are updated the widget only + widgets: [ + ...preWidget, + { + ...selectedWidget, + description: updatedDescription, + id: widgetId, + isStacked: updatedisStacked, + nullZeroValues: updatednullZeroValues, + opacity: updatedopacity, + title: updatedTitle, + timePreferance: updatedtimePreferance, + yAxisUnit: updatedYAxisUnit, + queryData: { + ...selectedWidget.queryData, + data: [ + ...selectedWidget.queryData.data.map((e) => ({ + ...e, + queryData: [], + })), + ], + }, }, - }, - ...afterWidget, - ], + ...afterWidget, + ], + }, + uuid, }); if (response.statusCode === 200) { diff --git a/frontend/src/store/actions/dashboard/updateDashboardTitle.ts b/frontend/src/store/actions/dashboard/updateDashboardTitle.ts index dc1b24fb96..277ddf7629 100644 --- a/frontend/src/store/actions/dashboard/updateDashboardTitle.ts +++ b/frontend/src/store/actions/dashboard/updateDashboardTitle.ts @@ -13,8 +13,10 @@ export const UpdateDashboardTitleDescriptionTags = ({ const { data } = dashboard; const response = await update({ - ...dashboard.data, - title: dashboard.data.title, + data: { + ...dashboard.data, + title: dashboard.data.title, + }, uuid: dashboard.uuid, }); diff --git a/frontend/src/store/actions/global.ts b/frontend/src/store/actions/global.ts index c3f3180567..c2b88919da 100644 --- a/frontend/src/store/actions/global.ts +++ b/frontend/src/store/actions/global.ts @@ -1,4 +1,4 @@ -import { Time } from 'container/Header/DateTimeSelection/config'; +import { Time } from 'container/TopNav/DateTimeSelection/config'; import GetMinMax from 'lib/getMinMax'; import { Dispatch } from 'redux'; import AppActions from 'types/actions'; diff --git a/frontend/src/store/actions/metrics/getInitialData.ts b/frontend/src/store/actions/metrics/getInitialData.ts index 27672f63a4..f994a35c94 100644 --- a/frontend/src/store/actions/metrics/getInitialData.ts +++ b/frontend/src/store/actions/metrics/getInitialData.ts @@ -12,6 +12,7 @@ import { AppState } from 'store/reducers'; import AppActions from 'types/actions'; import { Props } from 'types/api/metrics/getDBOverview'; import { GlobalReducer } from 'types/reducer/globalTime'; +import { Tags } from 'types/reducer/trace'; export const GetInitialData = ( props: GetInitialDataProps, @@ -64,11 +65,13 @@ export const GetInitialData = ( service: props.serviceName, start: minTime, step: getStep({ start: minTime, end: maxTime, inputFormat: 'ns' }), + selectedTags: props.selectedTags, }), getTopEndPoints({ end: maxTime, service: props.serviceName, start: minTime, + selectedTags: props.selectedTags, }), ]); @@ -121,4 +124,5 @@ export interface GetInitialDataProps { serviceName: Props['service']; maxTime: GlobalReducer['maxTime']; minTime: GlobalReducer['minTime']; + selectedTags: Tags[]; } diff --git a/frontend/src/store/actions/metrics/getService.ts b/frontend/src/store/actions/metrics/getService.ts index bdc751a777..82a3d7e820 100644 --- a/frontend/src/store/actions/metrics/getService.ts +++ b/frontend/src/store/actions/metrics/getService.ts @@ -5,6 +5,7 @@ import { Dispatch } from 'redux'; import { AppState } from 'store/reducers'; import AppActions from 'types/actions'; import { GlobalReducer } from 'types/reducer/globalTime'; +import { Tags } from 'types/reducer/trace'; export const GetService = ( props: GetServiceProps, @@ -32,6 +33,7 @@ export const GetService = ( const response = await getService({ end: maxTime, start: minTime, + selectedTags: props.selectedTags, }); if (response.statusCode === 200) { @@ -61,4 +63,5 @@ export const GetService = ( export type GetServiceProps = { minTime: GlobalReducer['minTime']; maxTime: GlobalReducer['maxTime']; + selectedTags: Tags[]; }; diff --git a/frontend/src/store/actions/metrics/setResourceAttributeQueries.ts b/frontend/src/store/actions/metrics/setResourceAttributeQueries.ts new file mode 100644 index 0000000000..14b262d4b6 --- /dev/null +++ b/frontend/src/store/actions/metrics/setResourceAttributeQueries.ts @@ -0,0 +1,55 @@ +import { IResourceAttributeQuery } from 'container/MetricsApplication/ResourceAttributesFilter/types'; +import { decode, encode } from 'js-base64'; +import history from 'lib/history'; +import { resourceAttributesQueryToPromQL } from 'lib/resourceAttributes'; +import { SET_RESOURCE_ATTRIBUTE_QUERIES } from 'types/actions/metrics'; + +export function GetResourceAttributeQueriesFromURL(): + | IResourceAttributeQuery[] + | null { + const resourceAttributeQuery = new URLSearchParams( + history.location.search, + ).get('resourceAttribute'); + + try { + if (resourceAttributeQuery) { + return JSON.parse( + decode(resourceAttributeQuery), + ) as IResourceAttributeQuery[]; + } + } catch (error) { + console.error(error); + } + + return null; +} + +export const SetResourceAttributeQueriesFromURL = ( + queries: IResourceAttributeQuery[], +): void => { + history.push({ + pathname: history.location.pathname, + search: + queries && queries.length + ? `?resourceAttribute=${encode(JSON.stringify(queries))}` + : '', + }); +}; +export const SetResourceAttributeQueries = ( + queries: IResourceAttributeQuery[], +): { + type: typeof SET_RESOURCE_ATTRIBUTE_QUERIES; + payload: { + queries: IResourceAttributeQuery[]; + promQLQuery: string; + }; +} => { + SetResourceAttributeQueriesFromURL(queries); + return { + type: SET_RESOURCE_ATTRIBUTE_QUERIES, + payload: { + queries, + promQLQuery: resourceAttributesQueryToPromQL(queries), + }, + }; +}; diff --git a/frontend/src/store/actions/serviceMap.ts b/frontend/src/store/actions/serviceMap.ts index a6f079a7eb..36d8e5ba97 100644 --- a/frontend/src/store/actions/serviceMap.ts +++ b/frontend/src/store/actions/serviceMap.ts @@ -47,13 +47,17 @@ export interface ServiceMapLoading { export const getDetailedServiceMapItems = (globalTime: GlobalTime) => { return async (dispatch: Dispatch): Promise => { - const requestString = `/services?start=${globalTime.minTime}&end=${globalTime.maxTime}`; - - const serviceMapDependencies = `/serviceMapDependencies?start=${globalTime.minTime}&end=${globalTime.maxTime}`; + const start = `${globalTime.minTime}`; + const end = `${globalTime.maxTime}`; + const serviceMapPayload = { + start, + end, + tags: [], + }; const [serviceMapDependenciesResponse, response] = await Promise.all([ - api.get(serviceMapDependencies), - api.get(requestString), + api.post(`/serviceMapDependencies`, serviceMapPayload), + api.post(`/services`, serviceMapPayload), ]); dispatch({ diff --git a/frontend/src/store/actions/trace/getInitialFilter.ts b/frontend/src/store/actions/trace/getInitialFilter.ts index 9b189cf60c..fcc9c08b1c 100644 --- a/frontend/src/store/actions/trace/getInitialFilter.ts +++ b/frontend/src/store/actions/trace/getInitialFilter.ts @@ -13,12 +13,14 @@ import { TraceFilterEnum, TraceReducer } from 'types/reducer/trace'; import { isTraceFilterEnum, + parseAggregateOrderParams, parseFilterExclude, parseFilterToFetchData, parseIsSkippedSelection, parseQueryIntoCurrent, parseQueryIntoFilter, parseQueryIntoOrder, + parseQueryIntoPageSize, parseQueryIntoSelectedTags, parseSelectedFilter, } from './util'; @@ -72,6 +74,11 @@ export const GetInitialTraceFilter = ( traces.spansAggregate.order, ); + const parsedPageSize = parseQueryIntoPageSize( + query, + traces.spansAggregate.pageSize, + ); + const isSelectionSkipped = parseIsSkippedSelection(query); const parsedSelectedTags = parseQueryIntoSelectedTags( @@ -79,6 +86,11 @@ export const GetInitialTraceFilter = ( traces.selectedTags, ); + const parsedOrderParams = parseAggregateOrderParams( + query, + traces.spansAggregate.orderParam, + ); + const parsedFilter = parseQueryIntoFilter(query, traces.filter); // now filter are not matching we need to fetch the data and make in sync @@ -155,6 +167,8 @@ export const GetInitialTraceFilter = ( userSelected: getUserSelected.currentValue, isFilterExclude: getIsFilterExcluded.currentValue, order: parsedQueryOrder.currentValue, + pageSize: parsedPageSize.currentValue, + orderParam: parsedOrderParams.currentValue, }, }); } else { diff --git a/frontend/src/store/actions/trace/getInitialSpansAggregate.ts b/frontend/src/store/actions/trace/getInitialSpansAggregate.ts index e723a84939..fff9a01902 100644 --- a/frontend/src/store/actions/trace/getInitialSpansAggregate.ts +++ b/frontend/src/store/actions/trace/getInitialSpansAggregate.ts @@ -31,7 +31,7 @@ export const GetSpansAggregate = ( return; } - const order = props.order === 'ascending' ? 'ascend' : 'descend'; + const { order = '' } = props; try { // triggering loading @@ -46,6 +46,7 @@ export const GetSpansAggregate = ( total: spansAggregate.total, pageSize: props.pageSize, order, + orderParam: spansAggregate.orderParam, }, }, }); @@ -59,6 +60,7 @@ export const GetSpansAggregate = ( selectedTags: props.selectedTags, isFilterExclude: traces.isFilterExclude, order, + orderParam: props.orderParam, }); if (response.statusCode === 200) { @@ -73,6 +75,7 @@ export const GetSpansAggregate = ( total: response.payload.totalSpans, pageSize: props.pageSize, order, + orderParam: spansAggregate.orderParam, }, }, }); @@ -85,6 +88,8 @@ export const GetSpansAggregate = ( traces.isFilterExclude, traces.userSelectedFilter, order, + traces.spansAggregate.pageSize, + spansAggregate.orderParam, ); } else { notification.error({ @@ -102,6 +107,7 @@ export const GetSpansAggregate = ( total: spansAggregate.total, pageSize: props.pageSize, order, + orderParam: spansAggregate.orderParam, }, }, }); @@ -118,6 +124,7 @@ export const GetSpansAggregate = ( total: spansAggregate.total, pageSize: props.pageSize, order, + orderParam: spansAggregate.orderParam, }, }, }); @@ -133,4 +140,5 @@ export interface GetSpansAggregateProps { pageSize: TraceReducer['spansAggregate']['pageSize']; selectedTags: TraceReducer['selectedTags']; order: GetSpanAggregateProps['order']; + orderParam: GetSpanAggregateProps['orderParam']; } diff --git a/frontend/src/store/actions/trace/parseFilter/index.ts b/frontend/src/store/actions/trace/parseFilter/index.ts index f2489c2076..f4db0bef3c 100644 --- a/frontend/src/store/actions/trace/parseFilter/index.ts +++ b/frontend/src/store/actions/trace/parseFilter/index.ts @@ -6,4 +6,6 @@ export * from './selectedFilter'; export * from './selectedTags'; export * from './skippedSelected'; export * from './spanAggregateCurrentPage'; +export * from './spanAggregateCurrentPageSize'; export * from './spanAggregateOrder'; +export * from './spanAggregateOrderParam'; diff --git a/frontend/src/store/actions/trace/parseFilter/spanAggregateCurrentPageSize.ts b/frontend/src/store/actions/trace/parseFilter/spanAggregateCurrentPageSize.ts new file mode 100644 index 0000000000..6bdabe7870 --- /dev/null +++ b/frontend/src/store/actions/trace/parseFilter/spanAggregateCurrentPageSize.ts @@ -0,0 +1,37 @@ +import { TraceReducer } from 'types/reducer/trace'; + +import { ParsedUrl } from '../util'; + +export const parseQueryIntoPageSize = ( + query: string, + stateCurrent: TraceReducer['spansAggregate']['pageSize'], +): ParsedUrl => { + const url = new URLSearchParams(query); + + let current = 1; + + const selected = url.get('spanAggregateCurrentPageSize'); + + if (selected) { + try { + const parsedValue = JSON.parse(decodeURIComponent(selected)); + if (Number.isInteger(parsedValue)) { + current = parseInt(parsedValue, 10); + } + } catch (error) { + console.log('error while parsing json'); + } + } + + if (selected) { + return { + currentValue: parseInt(selected, 10), + urlValue: current, + }; + } + + return { + currentValue: stateCurrent, + urlValue: current, + }; +}; diff --git a/frontend/src/store/actions/trace/parseFilter/spanAggregateOrder.ts b/frontend/src/store/actions/trace/parseFilter/spanAggregateOrder.ts index 1641ebe8f2..47d55bd218 100644 --- a/frontend/src/store/actions/trace/parseFilter/spanAggregateOrder.ts +++ b/frontend/src/store/actions/trace/parseFilter/spanAggregateOrder.ts @@ -8,7 +8,7 @@ export const parseQueryIntoOrder = ( ): ParsedUrl => { const url = new URLSearchParams(query); - let current = 'ascend'; + let current = ''; const selected = url.get('spanAggregateOrder'); diff --git a/frontend/src/store/actions/trace/parseFilter/spanAggregateOrderParam.ts b/frontend/src/store/actions/trace/parseFilter/spanAggregateOrderParam.ts new file mode 100644 index 0000000000..129fc2b9f8 --- /dev/null +++ b/frontend/src/store/actions/trace/parseFilter/spanAggregateOrderParam.ts @@ -0,0 +1,39 @@ +import { TraceReducer } from 'types/reducer/trace'; + +import { ParsedUrl } from '../util'; + +export const parseAggregateOrderParams = ( + query: string, + stateCurrent: TraceReducer['spansAggregate']['orderParam'], +): ParsedUrl => { + const url = new URLSearchParams(query); + + let current = ''; + + const selected = url.get('spanAggregateOrderParam'); + + if (selected) { + try { + const parsedValue = selected; + + if (parsedValue && typeof parsedValue === 'string') { + current = parsedValue; + } + } catch (error) { + console.log(error); + console.log('error while parsing json'); + } + } + + if (selected) { + return { + currentValue: current, + urlValue: current, + }; + } + + return { + currentValue: stateCurrent, + urlValue: current, + }; +}; diff --git a/frontend/src/store/actions/trace/selectTraceFilter.ts b/frontend/src/store/actions/trace/selectTraceFilter.ts index 23cf27d648..d11b12022a 100644 --- a/frontend/src/store/actions/trace/selectTraceFilter.ts +++ b/frontend/src/store/actions/trace/selectTraceFilter.ts @@ -47,6 +47,8 @@ export const SelectedTraceFilter = (props: { traces.isFilterExclude, traces.userSelectedFilter, traces.spansAggregate.order, + traces.spansAggregate.pageSize, + traces.spansAggregate.orderParam, ); }; }; diff --git a/frontend/src/store/actions/trace/util.ts b/frontend/src/store/actions/trace/util.ts index 1693f2693a..20e677ad97 100644 --- a/frontend/src/store/actions/trace/util.ts +++ b/frontend/src/store/actions/trace/util.ts @@ -23,6 +23,8 @@ export const updateURL = ( isFilterExclude: TraceReducer['isFilterExclude'], userSelectedFilter: TraceReducer['userSelectedFilter'], spanAggregateOrder: TraceReducer['spansAggregate']['order'], + spanAggregateCurrentPageSize: TraceReducer['spansAggregate']['pageSize'], + spanAggregateOrderParam: TraceReducer['spansAggregate']['orderParam'], ): void => { const search = new URLSearchParams(window.location.search); const preResult: { key: string; value: string }[] = []; @@ -36,6 +38,8 @@ export const updateURL = ( 'userSelectedFilter', 'spanAggregateCurrentPage', 'spanAggregateOrder', + 'spanAggregateCurrentPageSize', + 'spanAggregateOrderParam', ]; search.forEach((value, key) => { @@ -60,7 +64,7 @@ export const updateURL = ( Object.fromEntries(isFilterExclude), )}&userSelectedFilter=${JSON.stringify( Object.fromEntries(userSelectedFilter), - )}&spanAggregateCurrentPage=${spanAggregateCurrentPage}&spanAggregateOrder=${spanAggregateOrder}`, + )}&spanAggregateCurrentPage=${spanAggregateCurrentPage}&spanAggregateOrder=${spanAggregateOrder}&spanAggregateCurrentPageSize=${spanAggregateCurrentPageSize}&spanAggregateOrderParam=${spanAggregateOrderParam}`, ); }; diff --git a/frontend/src/store/reducers/app.ts b/frontend/src/store/reducers/app.ts index a8feece5a4..8c35b748b0 100644 --- a/frontend/src/store/reducers/app.ts +++ b/frontend/src/store/reducers/app.ts @@ -1,7 +1,8 @@ import getLocalStorageKey from 'api/browser/localstorage/get'; import { IS_SIDEBAR_COLLAPSED } from 'constants/app'; -import { IS_LOGGED_IN } from 'constants/auth'; +import { LOCALSTORAGE } from 'constants/localStorage'; import getTheme from 'lib/theme/getTheme'; +import { getInitialUserTokenRefreshToken } from 'store/utils'; import { AppAction, LOGGED_IN, @@ -11,17 +12,47 @@ import { UPDATE_CURRENT_VERSION, UPDATE_LATEST_VERSION, UPDATE_LATEST_VERSION_ERROR, + UPDATE_ORG_NAME, + UPDATE_USER, + UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN, + UPDATE_USER_IS_FETCH, + UPDATE_USER_ORG_ROLE, } from 'types/actions/app'; -import InitialValueTypes from 'types/reducer/app'; +import { + Organization, + PayloadProps as OrgPayload, +} from 'types/api/user/getOrganization'; +import InitialValueTypes, { User } from 'types/reducer/app'; + +const getInitialUser = (): User | null => { + const response = getInitialUserTokenRefreshToken(); + + if (response) { + return { + accessJwt: response.accessJwt, + refreshJwt: response.refreshJwt, + userId: '', + email: '', + name: '', + profilePictureURL: '', + }; + } + return null; +}; const InitialValue: InitialValueTypes = { isDarkMode: getTheme() === 'darkMode', - isLoggedIn: getLocalStorageKey(IS_LOGGED_IN) === 'yes', + isLoggedIn: getLocalStorageKey(LOCALSTORAGE.IS_LOGGED_IN) === 'true', isSideBarCollapsed: getLocalStorageKey(IS_SIDEBAR_COLLAPSED) === 'true', currentVersion: '', latestVersion: '', isCurrentVersionError: false, isLatestVersionError: false, + user: getInitialUser(), + isUserFetching: true, + isUserFetchingError: false, + org: null, + role: null, }; const appReducer = ( @@ -39,7 +70,7 @@ const appReducer = ( case LOGGED_IN: { return { ...state, - isLoggedIn: true, + isLoggedIn: action.payload.isLoggedIn, }; } @@ -72,6 +103,93 @@ const appReducer = ( }; } + case UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN: { + return { + ...state, + user: { + userId: '', + email: '', + name: '', + profilePictureURL: '', + ...action.payload, + }, + }; + } + + case UPDATE_USER_IS_FETCH: { + return { + ...state, + isUserFetching: action.payload.isUserFetching, + }; + } + + case UPDATE_USER_ORG_ROLE: { + return { + ...state, + ...action.payload, + }; + } + + case UPDATE_USER: { + const user = state.user || ({} as User); + const org = state.org || ([] as Organization[]); + const { + email, + name, + profilePictureURL, + userId, + ROLE, + orgId, + orgName, + } = action.payload; + const orgIndex = org.findIndex((e) => e.id === orgId); + + const updatedOrg: OrgPayload = [ + ...org.slice(0, orgIndex), + { + createdAt: 0, + hasOptedUpdates: false, + id: orgId, + isAnonymous: false, + name: orgName, + }, + ...org.slice(orgIndex + 1, org.length), + ]; + + return { + ...state, + user: { + ...user, + email, + name, + profilePictureURL, + userId, + }, + org: [...updatedOrg], + role: ROLE, + }; + } + + case UPDATE_ORG_NAME: { + const stateOrg = state.org || ({} as OrgPayload); + const { index, name: updatedName } = action.payload; + const current = stateOrg[index]; + + const updatedOrg: OrgPayload = [ + ...stateOrg.slice(0, index), + { + ...current, + name: updatedName, + }, + ...stateOrg.slice(index + 1, stateOrg.length), + ]; + + return { + ...state, + org: updatedOrg, + }; + } + default: return state; } diff --git a/frontend/src/store/reducers/global.ts b/frontend/src/store/reducers/global.ts index 1ba2e246e7..084e7cd377 100644 --- a/frontend/src/store/reducers/global.ts +++ b/frontend/src/store/reducers/global.ts @@ -1,4 +1,4 @@ -import { getDefaultOption } from 'container/Header/DateTimeSelection/config'; +import { getDefaultOption } from 'container/TopNav/DateTimeSelection/config'; import { GLOBAL_TIME_LOADING_START, GlobalTimeAction, diff --git a/frontend/src/store/reducers/metric.ts b/frontend/src/store/reducers/metric.ts index d12ca9c2bf..72b24a6b5b 100644 --- a/frontend/src/store/reducers/metric.ts +++ b/frontend/src/store/reducers/metric.ts @@ -1,3 +1,5 @@ +import { resourceAttributesQueryToPromQL } from 'lib/resourceAttributes'; +import { GetResourceAttributeQueriesFromURL } from 'store/actions/metrics/setResourceAttributeQueries'; import { GET_INITIAL_APPLICATION_ERROR, GET_INITIAL_APPLICATION_LOADING, @@ -7,6 +9,7 @@ import { GET_SERVICE_LIST_SUCCESS, MetricsActions, RESET_INITIAL_APPLICATION_DATA, + SET_RESOURCE_ATTRIBUTE_QUERIES, } from 'types/actions/metrics'; import InitialValueTypes from 'types/reducer/metrics'; @@ -22,6 +25,10 @@ const InitialValue: InitialValueTypes = { externalAverageDuration: [], externalError: [], serviceOverview: [], + resourceAttributeQueries: GetResourceAttributeQueriesFromURL() || [], + resourceAttributePromQLQuery: resourceAttributesQueryToPromQL( + GetResourceAttributeQueriesFromURL() || [], + ), }; const metrics = ( @@ -73,6 +80,8 @@ const metrics = ( case RESET_INITIAL_APPLICATION_DATA: { return { ...InitialValue, + // ...state.resourceAttributeQueries, + // resourceAttributeQueries: state.resourceAttributeQueries, }; } @@ -97,6 +106,16 @@ const metrics = ( metricsApplicationLoading: false, }; } + + case SET_RESOURCE_ATTRIBUTE_QUERIES: { + const { queries, promQLQuery } = action.payload; + return { + ...state, + resourceAttributeQueries: queries, + resourceAttributePromQLQuery: promQLQuery, + }; + } + default: return state; } diff --git a/frontend/src/store/reducers/trace.ts b/frontend/src/store/reducers/trace.ts index 010813c16e..b99ee7dd3a 100644 --- a/frontend/src/store/reducers/trace.ts +++ b/frontend/src/store/reducers/trace.ts @@ -10,8 +10,10 @@ import { UPDATE_SELECTED_GROUP_BY, UPDATE_SELECTED_TAGS, UPDATE_SPAN_ORDER, + UPDATE_SPAN_ORDER_PARAMS, UPDATE_SPANS_AGGREGATE, UPDATE_SPANS_AGGREGATE_PAGE_NUMBER, + UPDATE_SPANS_AGGREGATE_PAGE_SIZE, UPDATE_TAG_MODAL_VISIBILITY, UPDATE_TRACE_FILTER, UPDATE_TRACE_FILTER_LOADING, @@ -39,7 +41,8 @@ const initialValue: TraceReducer = { error: false, total: 0, pageSize: 10, - order: 'ascend', + order: '', + orderParam: '', }, selectedGroupBy: '', selectedFunction: 'count', @@ -75,6 +78,8 @@ const traceReducer = ( userSelected, isFilterExclude, order, + pageSize, + orderParam, } = payload; return { @@ -88,7 +93,9 @@ const traceReducer = ( spansAggregate: { ...state.spansAggregate, currentPage: current, + pageSize, order, + orderParam, }, }; } @@ -224,6 +231,26 @@ const traceReducer = ( }; } + case UPDATE_SPANS_AGGREGATE_PAGE_SIZE: { + return { + ...state, + spansAggregate: { + ...state.spansAggregate, + pageSize: action.payload.pageSize, + }, + }; + } + + case UPDATE_SPAN_ORDER_PARAMS: { + return { + ...state, + spansAggregate: { + ...state.spansAggregate, + orderParam: action.payload.orderParam, + }, + }; + } + default: return state; } diff --git a/frontend/src/store/utils.ts b/frontend/src/store/utils.ts new file mode 100644 index 0000000000..22e3942151 --- /dev/null +++ b/frontend/src/store/utils.ts @@ -0,0 +1,22 @@ +import getLocalStorageKey from 'api/browser/localstorage/get'; +import { LOCALSTORAGE } from 'constants/localStorage'; +import { User } from 'types/reducer/app'; + +export const getInitialUserTokenRefreshToken = (): AuthTokenProps | null => { + const accessJwt = getLocalStorageKey(LOCALSTORAGE.AUTH_TOKEN); + const refreshJwt = getLocalStorageKey(LOCALSTORAGE.REFRESH_AUTH_TOKEN); + + if (accessJwt && refreshJwt) { + return { + accessJwt, + refreshJwt, + }; + } + + return null; +}; + +interface AuthTokenProps { + accessJwt: User['accessJwt']; + refreshJwt: User['refreshJwt']; +} diff --git a/frontend/src/types/actions/app.ts b/frontend/src/types/actions/app.ts index b07cde36d4..67036478c6 100644 --- a/frontend/src/types/actions/app.ts +++ b/frontend/src/types/actions/app.ts @@ -1,4 +1,9 @@ -import AppReducer from 'types/reducer/app'; +import { + Organization, + PayloadProps as OrgPayload, +} from 'types/api/user/getOrganization'; +import AppReducer, { User } from 'types/reducer/app'; +import { ROLES } from 'types/roles'; export const SWITCH_DARK_MODE = 'SWITCH_DARK_MODE'; export const LOGGED_IN = 'LOGGED_IN'; @@ -9,6 +14,12 @@ export const UPDATE_LATEST_VERSION = 'UPDATE_LATEST_VERSION'; export const UPDATE_CURRENT_ERROR = 'UPDATE_CURRENT_ERROR'; export const UPDATE_LATEST_VERSION_ERROR = 'UPDATE_LATEST_VERSION_ERROR'; +export const UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN = + 'UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN'; +export const UPDATE_USER_IS_FETCH = 'UPDATE_USER_IS_FETCH'; +export const UPDATE_USER_ORG_ROLE = 'UPDATE_USER_ORG_ROLE'; +export const UPDATE_USER = 'UPDATE_USER'; +export const UPDATE_ORG_NAME = 'UPDATE_ORG_NAME'; export interface SwitchDarkMode { type: typeof SWITCH_DARK_MODE; @@ -16,6 +27,9 @@ export interface SwitchDarkMode { export interface LoggedInUser { type: typeof LOGGED_IN; + payload: { + isLoggedIn: boolean; + }; } export interface SideBarCollapse { @@ -44,10 +58,59 @@ export interface UpdateVersionError { }; } +export interface UpdateUserOrgRole { + type: typeof UPDATE_USER_ORG_ROLE; + payload: { + role: ROLES | null; + org: OrgPayload | null; + }; +} + +export interface UpdateAccessRenewToken { + type: typeof UPDATE_USER_ACCESS_REFRESH_ACCESS_TOKEN; + payload: { + accessJwt: User['accessJwt']; + refreshJwt: User['refreshJwt']; + }; +} + +export interface UpdateUser { + type: typeof UPDATE_USER; + payload: { + email: User['email']; + name: User['name']; + profilePictureURL: User['profilePictureURL']; + userId: User['userId']; + orgName: Organization['name']; + ROLE: ROLES; + orgId: Organization['id']; + }; +} + +export interface UpdateUserIsFetched { + type: typeof UPDATE_USER_IS_FETCH; + payload: { + isUserFetching: AppReducer['isUserFetching']; + }; +} + +export interface UpdateOrgName { + type: typeof UPDATE_ORG_NAME; + payload: { + name: string; + index: number; + }; +} + export type AppAction = | SwitchDarkMode | LoggedInUser | SideBarCollapse | UpdateAppVersion | UpdateLatestVersion - | UpdateVersionError; + | UpdateVersionError + | UpdateAccessRenewToken + | UpdateUserIsFetched + | UpdateUserOrgRole + | UpdateUser + | UpdateOrgName; diff --git a/frontend/src/types/actions/globalTime.ts b/frontend/src/types/actions/globalTime.ts index 8bc148fa1b..8e384fb569 100644 --- a/frontend/src/types/actions/globalTime.ts +++ b/frontend/src/types/actions/globalTime.ts @@ -1,4 +1,4 @@ -import { Time } from 'container/Header/DateTimeSelection/config'; +import { Time } from 'container/TopNav/DateTimeSelection/config'; export const UPDATE_TIME_INTERVAL = 'UPDATE_TIME_INTERVAL'; export const GLOBAL_TIME_LOADING_START = 'GLOBAL_TIME_LOADING_START'; diff --git a/frontend/src/types/actions/metrics.ts b/frontend/src/types/actions/metrics.ts index d9dd162218..382e56b560 100644 --- a/frontend/src/types/actions/metrics.ts +++ b/frontend/src/types/actions/metrics.ts @@ -2,6 +2,7 @@ // import { ExternalAverageDuration } from 'types/api/metrics/getExternalAverageDuration'; // import { ExternalError } from 'types/api/metrics/getExternalError'; // import { ExternalService } from 'types/api/metrics/getExternalService'; +import { IResourceAttributeQuery } from 'container/MetricsApplication/ResourceAttributesFilter/types'; import { ServicesList } from 'types/api/metrics/getService'; import { ServiceOverview } from 'types/api/metrics/getServiceOverview'; import { TopEndPoints } from 'types/api/metrics/getTopEndPoints'; @@ -14,6 +15,8 @@ export const GET_INITIAL_APPLICATION_LOADING = export const GET_INITIAL_APPLICATION_ERROR = 'GET_INITIAL_APPLICATION_ERROR'; export const GET_INTIAL_APPLICATION_DATA = 'GET_INTIAL_APPLICATION_DATA'; export const RESET_INITIAL_APPLICATION_DATA = 'RESET_INITIAL_APPLICATION_DATA'; +export const SET_RESOURCE_ATTRIBUTE_QUERIES = 'SET_RESOURCE_ATTRIBUTE_QUERIES'; + export interface GetServiceList { type: typeof GET_SERVICE_LIST_SUCCESS; payload: ServicesList[]; @@ -48,9 +51,18 @@ export interface ResetInitialApplicationData { type: typeof RESET_INITIAL_APPLICATION_DATA; } +export interface SetResourceAttributeQueries { + type: typeof SET_RESOURCE_ATTRIBUTE_QUERIES; + payload: { + queries: IResourceAttributeQuery[]; + promQLQuery: string; + }; +} + export type MetricsActions = | GetServiceListError | GetServiceListLoading | GetServiceList | GetInitialApplicationData - | ResetInitialApplicationData; + | ResetInitialApplicationData + | SetResourceAttributeQueries; diff --git a/frontend/src/types/actions/trace.ts b/frontend/src/types/actions/trace.ts index cfdb86bcbb..da97d05129 100644 --- a/frontend/src/types/actions/trace.ts +++ b/frontend/src/types/actions/trace.ts @@ -28,6 +28,9 @@ export const UPDATE_FILTER_EXCLUDE = 'UPDATE_FILTER_EXCLUDE'; export const UPDATE_SPAN_ORDER = 'UPDATE_SPAN_ORDER'; export const UPDATE_SPANS_AGGREGATE_PAGE_NUMBER = 'UPDATE_SPANS_AGGREGATE_PAGE_NUMBER'; +export const UPDATE_SPANS_AGGREGATE_PAGE_SIZE = + 'UPDATE_SPANS_AGGREGATE_PAGE_SIZE'; +export const UPDATE_SPAN_ORDER_PARAMS = 'UPDATE_SPAN_ORDER_PARAMS'; export interface UpdateFilter { type: typeof UPDATE_TRACE_FILTER; @@ -75,6 +78,8 @@ export interface UpdateAllFilters { userSelected: TraceReducer['userSelectedFilter']; isFilterExclude: TraceReducer['isFilterExclude']; order: TraceReducer['spansAggregate']['order']; + pageSize: TraceReducer['spansAggregate']['pageSize']; + orderParam: TraceReducer['spansAggregate']['orderParam']; }; } @@ -168,6 +173,20 @@ export interface UpdateSpanOrder { }; } +export interface UpdateSpanSize { + type: typeof UPDATE_SPANS_AGGREGATE_PAGE_SIZE; + payload: { + pageSize: TraceReducer['spansAggregate']['pageSize']; + }; +} + +export interface UpdateSpanParams { + type: typeof UPDATE_SPAN_ORDER_PARAMS; + payload: { + orderParam: TraceReducer['spansAggregate']['orderParam']; + }; +} + export type TraceActions = | UpdateFilter | GetTraceFilter @@ -187,4 +206,6 @@ export type TraceActions = | UpdateSelected | UpdateFilterExclude | UpdateSpanOrder - | UpdateSpansAggregatePageNumber; + | UpdateSpansAggregatePageNumber + | UpdateSpanSize + | UpdateSpanParams; diff --git a/frontend/src/types/api/channels/createPager.ts b/frontend/src/types/api/channels/createPager.ts new file mode 100644 index 0000000000..4a50cff577 --- /dev/null +++ b/frontend/src/types/api/channels/createPager.ts @@ -0,0 +1,8 @@ +import { PagerChannel } from 'container/CreateAlertChannels/config'; + +export type Props = PagerChannel; + +export interface PayloadProps { + data: string; + status: string; +} diff --git a/frontend/src/types/api/channels/editPager.ts b/frontend/src/types/api/channels/editPager.ts new file mode 100644 index 0000000000..8f9cd3ed11 --- /dev/null +++ b/frontend/src/types/api/channels/editPager.ts @@ -0,0 +1,10 @@ +import { PagerChannel } from 'container/CreateAlertChannels/config'; + +export interface Props extends PagerChannel { + id: string; +} + +export interface PayloadProps { + data: string; + status: string; +} diff --git a/frontend/src/types/api/dashboard/create.ts b/frontend/src/types/api/dashboard/create.ts index bd0996a0d6..3c859edc6c 100644 --- a/frontend/src/types/api/dashboard/create.ts +++ b/frontend/src/types/api/dashboard/create.ts @@ -1,8 +1,9 @@ -import { Dashboard } from './getAll'; +import { Dashboard, DashboardData } from './getAll'; -export type Props = { - uuid: Dashboard['uuid']; - title: Dashboard['data']['title']; -}; +export type Props = + | { + title: Dashboard['data']['title']; + } + | DashboardData; export type PayloadProps = Dashboard; diff --git a/frontend/src/types/api/dashboard/update.ts b/frontend/src/types/api/dashboard/update.ts index c58e3b5cd4..3839a84c88 100644 --- a/frontend/src/types/api/dashboard/update.ts +++ b/frontend/src/types/api/dashboard/update.ts @@ -1,8 +1,8 @@ import { Dashboard, DashboardData } from './getAll'; export type Props = { - title: Dashboard['data']['title']; uuid: Dashboard['uuid']; -} & DashboardData; + data: DashboardData; +}; export type PayloadProps = Dashboard; diff --git a/frontend/src/types/api/errors/getAll.ts b/frontend/src/types/api/errors/getAll.ts new file mode 100644 index 0000000000..98c3122f7d --- /dev/null +++ b/frontend/src/types/api/errors/getAll.ts @@ -0,0 +1,17 @@ +import { GlobalTime } from 'types/actions/globalTime'; + +export interface Props { + start: GlobalTime['minTime']; + end: GlobalTime['maxTime']; +} + +export interface Exception { + exceptionType: string; + exceptionMessage: string; + exceptionCount: number; + lastSeen: string; + firstSeen: string; + serviceName: string; +} + +export type PayloadProps = Exception[]; diff --git a/frontend/src/types/api/errors/getByErrorTypeAndService.ts b/frontend/src/types/api/errors/getByErrorTypeAndService.ts new file mode 100644 index 0000000000..4f987874b7 --- /dev/null +++ b/frontend/src/types/api/errors/getByErrorTypeAndService.ts @@ -0,0 +1,22 @@ +import { GlobalTime } from 'types/actions/globalTime'; + +export interface Props { + start: GlobalTime['minTime']; + end: GlobalTime['maxTime']; + serviceName: string; + errorType: string; +} + +export interface PayloadProps { + errorId: string; + exceptionType: string; + exceptionStacktrace: string; + exceptionEscaped: string; + exceptionMessage: string; + timestamp: string; + spanID: string; + traceID: string; + serviceName: Props['serviceName']; + newerErrorId: string; + olderErrorId: string; +} diff --git a/frontend/src/types/api/errors/getById.ts b/frontend/src/types/api/errors/getById.ts new file mode 100644 index 0000000000..c812410b89 --- /dev/null +++ b/frontend/src/types/api/errors/getById.ts @@ -0,0 +1,11 @@ +import { GlobalTime } from 'types/actions/globalTime'; + +import { PayloadProps as Payload } from './getByErrorTypeAndService'; + +export type PayloadProps = Payload; + +export type Props = { + start: GlobalTime['minTime']; + end: GlobalTime['minTime']; + errorId: string; +}; diff --git a/frontend/src/types/api/metrics/getDBOverview.ts b/frontend/src/types/api/metrics/getDBOverview.ts index 1e99b9688e..c677884245 100644 --- a/frontend/src/types/api/metrics/getDBOverview.ts +++ b/frontend/src/types/api/metrics/getDBOverview.ts @@ -1,8 +1,11 @@ +import { Tags } from 'types/reducer/trace'; + export interface Props { service: string; start: number; end: number; step: number; + selectedTags: Tags[]; } export interface DBOverView { diff --git a/frontend/src/types/api/metrics/getResourceAttributes.ts b/frontend/src/types/api/metrics/getResourceAttributes.ts new file mode 100644 index 0000000000..04b35255c9 --- /dev/null +++ b/frontend/src/types/api/metrics/getResourceAttributes.ts @@ -0,0 +1,8 @@ +export type TagKeysPayloadProps = { + data: string[]; +}; + +export type TagValueProps = string; +export type TagValuesPayloadProps = { + data: string[]; +}; diff --git a/frontend/src/types/api/metrics/getService.ts b/frontend/src/types/api/metrics/getService.ts index 869f56e2cb..030391874c 100644 --- a/frontend/src/types/api/metrics/getService.ts +++ b/frontend/src/types/api/metrics/getService.ts @@ -1,6 +1,9 @@ +import { Tags } from 'types/reducer/trace'; + export interface Props { start: number; end: number; + selectedTags: Tags[]; } export interface ServicesList { diff --git a/frontend/src/types/api/metrics/getTopEndPoints.ts b/frontend/src/types/api/metrics/getTopEndPoints.ts index cdc6cb3746..c86d5fd115 100644 --- a/frontend/src/types/api/metrics/getTopEndPoints.ts +++ b/frontend/src/types/api/metrics/getTopEndPoints.ts @@ -1,3 +1,5 @@ +import { Tags } from 'types/reducer/trace'; + export interface TopEndPoints { name: string; numCalls: number; @@ -10,6 +12,7 @@ export interface Props { service: string; start: number; end: number; + selectedTags: Tags[]; } export type PayloadProps = TopEndPoints[]; diff --git a/frontend/src/types/api/trace/getSpanAggregate.ts b/frontend/src/types/api/trace/getSpanAggregate.ts index 5f6d413a81..f5bcbdc6c5 100644 --- a/frontend/src/types/api/trace/getSpanAggregate.ts +++ b/frontend/src/types/api/trace/getSpanAggregate.ts @@ -9,6 +9,7 @@ export interface Props { selectedTags: TraceReducer['selectedTags']; order?: TraceReducer['spansAggregate']['order']; isFilterExclude: TraceReducer['isFilterExclude']; + orderParam: TraceReducer['spansAggregate']['orderParam']; } export type PayloadProps = { diff --git a/frontend/src/types/api/trace/getTagFilters.ts b/frontend/src/types/api/trace/getTagFilters.ts index 31581e98ba..29506d1509 100644 --- a/frontend/src/types/api/trace/getTagFilters.ts +++ b/frontend/src/types/api/trace/getTagFilters.ts @@ -1,9 +1,12 @@ +import { TraceReducer } from 'types/reducer/trace'; + export interface Props { start: number; end: number; other: { [k: string]: string[]; }; + isFilterExclude: TraceReducer['isFilterExclude']; } interface TagsKeys { diff --git a/frontend/src/types/api/user/changeMyPassword.ts b/frontend/src/types/api/user/changeMyPassword.ts new file mode 100644 index 0000000000..154d0b819a --- /dev/null +++ b/frontend/src/types/api/user/changeMyPassword.ts @@ -0,0 +1,11 @@ +import { User } from 'types/reducer/app'; + +export interface Props { + oldPassword: string; + newPassword: string; + userId: User['userId']; +} + +export interface PayloadProps { + data: string; +} diff --git a/frontend/src/types/api/user/deleteInvite.ts b/frontend/src/types/api/user/deleteInvite.ts new file mode 100644 index 0000000000..1caab7e694 --- /dev/null +++ b/frontend/src/types/api/user/deleteInvite.ts @@ -0,0 +1,9 @@ +import { User } from 'types/reducer/app'; + +export interface Props { + email: User['email']; +} + +export interface PayloadProps { + data: string; +} diff --git a/frontend/src/types/api/user/deleteUser.ts b/frontend/src/types/api/user/deleteUser.ts new file mode 100644 index 0000000000..a3eae2b19f --- /dev/null +++ b/frontend/src/types/api/user/deleteUser.ts @@ -0,0 +1,9 @@ +import { User } from 'types/reducer/app'; + +export interface Props { + userId: User['userId']; +} + +export interface PayloadProps { + data: string; +} diff --git a/frontend/src/types/api/user/editOrg.ts b/frontend/src/types/api/user/editOrg.ts new file mode 100644 index 0000000000..126211d7f5 --- /dev/null +++ b/frontend/src/types/api/user/editOrg.ts @@ -0,0 +1,10 @@ +export interface Props { + name: string; + isAnonymous: boolean; + orgId: string; + hasOptedUpdates?: boolean; +} + +export interface PayloadProps { + data: string; +} diff --git a/frontend/src/types/api/user/editUser.ts b/frontend/src/types/api/user/editUser.ts new file mode 100644 index 0000000000..a080214158 --- /dev/null +++ b/frontend/src/types/api/user/editUser.ts @@ -0,0 +1,10 @@ +import { User } from 'types/reducer/app'; + +import { PayloadProps as Payload } from './getUser'; + +export type PayloadProps = Payload; + +export interface Props { + userId: User['userId']; + name: User['name']; +} diff --git a/frontend/src/types/api/user/getInviteDetails.ts b/frontend/src/types/api/user/getInviteDetails.ts new file mode 100644 index 0000000000..224c73ca84 --- /dev/null +++ b/frontend/src/types/api/user/getInviteDetails.ts @@ -0,0 +1,17 @@ +import { User } from 'types/reducer/app'; +import { ROLES } from 'types/roles'; + +import { Organization } from './getOrganization'; + +export interface Props { + inviteId: string; +} + +export interface PayloadProps { + createdAt: number; + email: User['email']; + name: User['name']; + role: ROLES; + token: string; + organization: Organization['name']; +} diff --git a/frontend/src/types/api/user/getOrgMembers.ts b/frontend/src/types/api/user/getOrgMembers.ts new file mode 100644 index 0000000000..23b9dd2d30 --- /dev/null +++ b/frontend/src/types/api/user/getOrgMembers.ts @@ -0,0 +1,18 @@ +import { ROLES } from 'types/roles'; + +import { Organization } from './getOrganization'; + +export interface Props { + orgId: Organization['id']; +} + +interface OrgMembers { + createdAt: number; + email: string; + name: string; + role: ROLES; + token: string; + id: string; +} + +export type PayloadProps = OrgMembers[]; diff --git a/frontend/src/types/api/user/getOrganization.ts b/frontend/src/types/api/user/getOrganization.ts new file mode 100644 index 0000000000..efc855a82a --- /dev/null +++ b/frontend/src/types/api/user/getOrganization.ts @@ -0,0 +1,9 @@ +export interface Organization { + createdAt: number; + hasOptedUpdates: boolean; + id: string; + isAnonymous: boolean; + name: string; +} + +export type PayloadProps = Organization[]; diff --git a/frontend/src/types/api/user/getPendingInvites.ts b/frontend/src/types/api/user/getPendingInvites.ts new file mode 100644 index 0000000000..4b64bf79af --- /dev/null +++ b/frontend/src/types/api/user/getPendingInvites.ts @@ -0,0 +1,12 @@ +import { User } from 'types/reducer/app'; +import { ROLES } from 'types/roles'; + +export interface PendingInvite { + createdAt: number; + email: User['email']; + name: User['name']; + role: ROLES; + token: string; +} + +export type PayloadProps = PendingInvite[]; diff --git a/frontend/src/types/api/user/getResetPasswordToken.ts b/frontend/src/types/api/user/getResetPasswordToken.ts new file mode 100644 index 0000000000..dee9863006 --- /dev/null +++ b/frontend/src/types/api/user/getResetPasswordToken.ts @@ -0,0 +1,10 @@ +import { User } from 'types/reducer/app'; + +export interface Props { + userId: User['userId']; +} + +export interface PayloadProps { + token: string; + userId: string; +} diff --git a/frontend/src/types/api/user/getUser.ts b/frontend/src/types/api/user/getUser.ts new file mode 100644 index 0000000000..e066ec3103 --- /dev/null +++ b/frontend/src/types/api/user/getUser.ts @@ -0,0 +1,18 @@ +import { User } from 'types/reducer/app'; +import { ROLES } from 'types/roles'; + +export interface Props { + userId: User['userId']; + token?: string; +} + +export interface PayloadProps { + createdAt: number; + email: string; + id: string; + name: string; + orgId: string; + profilePictureURL: string; + organization: string; + role: ROLES; +} diff --git a/frontend/src/types/api/user/getUserRole.ts b/frontend/src/types/api/user/getUserRole.ts new file mode 100644 index 0000000000..b9a6b8dd80 --- /dev/null +++ b/frontend/src/types/api/user/getUserRole.ts @@ -0,0 +1,12 @@ +import { User } from 'types/reducer/app'; +import { ROLES } from 'types/roles'; + +export interface Props { + userId: User['userId']; + token?: string; +} + +export interface PayloadProps { + group_name: ROLES; + user_id: string; +} diff --git a/frontend/src/types/api/user/login.ts b/frontend/src/types/api/user/login.ts new file mode 100644 index 0000000000..db5f1875e9 --- /dev/null +++ b/frontend/src/types/api/user/login.ts @@ -0,0 +1,13 @@ +export interface PayloadProps { + accessJwt: string; + accessJwtExpiry: number; + refreshJwt: string; + refreshJwtExpiry: number; + userId: string; +} + +export interface Props { + email?: string; + password?: string; + refreshToken?: PayloadProps['refreshJwt']; +} diff --git a/frontend/src/types/api/user/resetPassword.ts b/frontend/src/types/api/user/resetPassword.ts new file mode 100644 index 0000000000..e97197acf0 --- /dev/null +++ b/frontend/src/types/api/user/resetPassword.ts @@ -0,0 +1,8 @@ +export interface Props { + token: string; + password: string; +} + +export interface PayloadProps { + data: string; +} diff --git a/frontend/src/types/api/user/setInvite.ts b/frontend/src/types/api/user/setInvite.ts new file mode 100644 index 0000000000..d9112e76b5 --- /dev/null +++ b/frontend/src/types/api/user/setInvite.ts @@ -0,0 +1,12 @@ +import { User } from 'types/reducer/app'; +import { ROLES } from 'types/roles'; + +export interface Props { + name: User['name']; + email: User['email']; + role: ROLES; +} + +export interface PayloadProps { + data: string; +} diff --git a/frontend/src/types/api/user/signup.ts b/frontend/src/types/api/user/signup.ts index 8e6feca0d6..809792042a 100644 --- a/frontend/src/types/api/user/signup.ts +++ b/frontend/src/types/api/user/signup.ts @@ -1,5 +1,7 @@ export interface Props { - email: string; name: string; - organizationName: string; + orgName: string; + email: string; + password: string; + token?: string; } diff --git a/frontend/src/types/api/user/updateRole.ts b/frontend/src/types/api/user/updateRole.ts new file mode 100644 index 0000000000..796ec2ed1d --- /dev/null +++ b/frontend/src/types/api/user/updateRole.ts @@ -0,0 +1,10 @@ +import { ROLES } from 'types/roles'; + +export interface Props { + group_name: ROLES; + userId: string; +} + +export interface PayloadProps { + data: string; +} diff --git a/frontend/src/types/reducer/app.ts b/frontend/src/types/reducer/app.ts index 68af65d075..9c1f16180e 100644 --- a/frontend/src/types/reducer/app.ts +++ b/frontend/src/types/reducer/app.ts @@ -1,3 +1,16 @@ +import { PayloadProps as OrgPayload } from 'types/api/user/getOrganization'; +import { PayloadProps as UserPayload } from 'types/api/user/getUser'; +import { ROLES } from 'types/roles'; + +export interface User { + accessJwt: string; + refreshJwt: string; + userId: string; + email: UserPayload['email']; + name: UserPayload['name']; + profilePictureURL: UserPayload['profilePictureURL']; +} + export default interface AppReducer { isDarkMode: boolean; isLoggedIn: boolean; @@ -6,4 +19,9 @@ export default interface AppReducer { latestVersion: string; isCurrentVersionError: boolean; isLatestVersionError: boolean; + user: null | User; + isUserFetching: boolean; + isUserFetchingError: boolean; + role: ROLES | null; + org: OrgPayload | null; } diff --git a/frontend/src/types/reducer/globalTime.ts b/frontend/src/types/reducer/globalTime.ts index 788b8447b4..cb55524c75 100644 --- a/frontend/src/types/reducer/globalTime.ts +++ b/frontend/src/types/reducer/globalTime.ts @@ -1,4 +1,4 @@ -import { Time } from 'container/Header/DateTimeSelection/config'; +import { Time } from 'container/TopNav/DateTimeSelection/config'; import { GlobalTime } from 'types/actions/globalTime'; export interface GlobalReducer { diff --git a/frontend/src/types/reducer/metrics.ts b/frontend/src/types/reducer/metrics.ts index 3519954f60..d5b500f109 100644 --- a/frontend/src/types/reducer/metrics.ts +++ b/frontend/src/types/reducer/metrics.ts @@ -1,3 +1,4 @@ +import { IResourceAttributeQuery } from 'container/MetricsApplication/ResourceAttributesFilter/types'; import { DBOverView } from 'types/api/metrics/getDBOverview'; import { ExternalAverageDuration } from 'types/api/metrics/getExternalAverageDuration'; import { ExternalError } from 'types/api/metrics/getExternalError'; @@ -18,6 +19,8 @@ interface MetricReducer { externalAverageDuration: ExternalAverageDuration[]; externalError: ExternalError[]; serviceOverview: ServiceOverview[]; + resourceAttributeQueries: IResourceAttributeQuery[]; + resourceAttributePromQLQuery: string; } export default MetricReducer; diff --git a/frontend/src/types/reducer/trace.ts b/frontend/src/types/reducer/trace.ts index 339fd63483..babeb344c6 100644 --- a/frontend/src/types/reducer/trace.ts +++ b/frontend/src/types/reducer/trace.ts @@ -21,6 +21,7 @@ export interface TraceReducer { total: number; pageSize: number; order: string; + orderParam: string; }; selectedGroupBy: string; selectedFunction: string; @@ -40,8 +41,8 @@ interface SpansAggregateData { serviceName: string; operation: string; durationNano: number; - httpCode: string; - httpMethod: string; + statusCode: string; + method: string; } export interface Tags { @@ -50,7 +51,12 @@ export interface Tags { Values: string[]; } -type OperatorValues = 'not in' | 'in'; +export interface TagsAPI { + Key: string; + Operator: OperatorValues; + Values: string[]; +} +export type OperatorValues = 'not in' | 'in'; export type TraceFilterEnum = | 'component' diff --git a/frontend/src/types/roles.ts b/frontend/src/types/roles.ts new file mode 100644 index 0000000000..02216f6d74 --- /dev/null +++ b/frontend/src/types/roles.ts @@ -0,0 +1,5 @@ +export type ADMIN = 'ADMIN'; +export type VIEWER = 'VIEWER'; +export type EDITOR = 'EDITOR'; + +export type ROLES = ADMIN | VIEWER | EDITOR; diff --git a/frontend/src/utils/permission/index.ts b/frontend/src/utils/permission/index.ts new file mode 100644 index 0000000000..a4fe82b395 --- /dev/null +++ b/frontend/src/utils/permission/index.ts @@ -0,0 +1,69 @@ +import ROUTES from 'constants/routes'; +import { ROLES } from 'types/roles'; + +export type ComponentTypes = + | 'current_org_settings' + | 'invite_members' + | 'create_new_dashboards' + | 'import_dashboard' + | 'export_dashboard' + | 'add_new_alert' + | 'add_new_channel' + | 'set_retention_period' + | 'action' + | 'save_layout' + | 'edit_dashboard' + | 'delete_widget' + | 'new_dashboard' + | 'new_alert_action' + | 'edit_widget'; + +export const componentPermission: Record = { + current_org_settings: ['ADMIN'], + invite_members: ['ADMIN'], + create_new_dashboards: ['ADMIN', 'EDITOR'], + import_dashboard: ['ADMIN', 'EDITOR'], + export_dashboard: ['ADMIN', 'EDITOR', 'VIEWER'], + add_new_alert: ['ADMIN', 'EDITOR'], + add_new_channel: ['ADMIN'], + set_retention_period: ['ADMIN'], + action: ['ADMIN', 'EDITOR'], + save_layout: ['ADMIN', 'EDITOR'], + edit_dashboard: ['ADMIN', 'EDITOR'], + delete_widget: ['ADMIN', 'EDITOR'], + new_dashboard: ['ADMIN', 'EDITOR'], + new_alert_action: ['ADMIN'], + edit_widget: ['ADMIN', 'EDITOR'], +}; + +export const routePermission: Record = { + ALERTS_NEW: ['ADMIN', 'EDITOR'], + ORG_SETTINGS: ['ADMIN'], + MY_SETTINGS: ['ADMIN', 'EDITOR', 'VIEWER'], + SERVICE_MAP: ['ADMIN', 'EDITOR', 'VIEWER'], + ALL_CHANNELS: ['ADMIN', 'EDITOR', 'VIEWER'], + ALL_DASHBOARD: ['ADMIN', 'EDITOR', 'VIEWER'], + ALL_ERROR: ['ADMIN', 'EDITOR', 'VIEWER'], + APPLICATION: ['ADMIN', 'EDITOR', 'VIEWER'], + CHANNELS_EDIT: ['ADMIN'], + CHANNELS_NEW: ['ADMIN'], + DASHBOARD: ['ADMIN', 'EDITOR', 'EDITOR'], + DASHBOARD_WIDGET: ['ADMIN', 'EDITOR', 'VIEWER'], + EDIT_ALERTS: ['ADMIN'], + ERROR_DETAIL: ['ADMIN', 'EDITOR', 'VIEWER'], + HOME_PAGE: ['ADMIN', 'EDITOR', 'VIEWER'], + INSTRUMENTATION: ['ADMIN', 'EDITOR', 'VIEWER'], + LIST_ALL_ALERT: ['ADMIN', 'EDITOR', 'VIEWER'], + LOGIN: ['ADMIN', 'EDITOR', 'VIEWER'], + NOT_FOUND: ['ADMIN', 'VIEWER', 'EDITOR'], + PASSWORD_RESET: ['ADMIN', 'EDITOR', 'VIEWER'], + SERVICE_METRICS: ['ADMIN', 'EDITOR', 'VIEWER'], + SETTINGS: ['ADMIN', 'EDITOR', 'VIEWER'], + SIGN_UP: ['ADMIN', 'EDITOR', 'VIEWER'], + SOMETHING_WENT_WRONG: ['ADMIN', 'EDITOR', 'VIEWER'], + TRACE: ['ADMIN', 'EDITOR', 'VIEWER'], + TRACE_DETAIL: ['ADMIN', 'EDITOR', 'VIEWER'], + UN_AUTHORIZED: ['ADMIN', 'EDITOR', 'VIEWER'], + USAGE_EXPLORER: ['ADMIN', 'EDITOR', 'VIEWER'], + VERSION: ['ADMIN', 'EDITOR', 'VIEWER'], +}; diff --git a/frontend/src/wdyr.ts b/frontend/src/wdyr.ts index 8ba678a4d6..e64dcca566 100644 --- a/frontend/src/wdyr.ts +++ b/frontend/src/wdyr.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable global-require */ /// // ^ https://github.com/welldone-software/why-did-you-render/issues/161 import React from 'react'; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index fcc3663f1b..1ac93ea555 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -22,5 +22,14 @@ "plugins": [{ "name": "typescript-plugin-css-modules" }] }, "exclude": ["node_modules"], - "include": ["./src", "./babel.config.js", "./jest.config.ts"] + "include": [ + "./src", + "./babel.config.js", + "./jest.config.ts", + "./.eslintrc.js", + "./__mocks__", + "./conf/default.conf", + "./public", + "./cypress" + ] } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index f7654074e0..6c42e58995 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2708,6 +2708,14 @@ resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d" integrity sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ== +"@xstate/react@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@xstate/react/-/react-3.0.0.tgz#888d9a6f128c70b632c18ad55f1f851f6ab092ba" + integrity sha512-KHSCfwtb8gZ7QH2luihvmKYI+0lcdHQOmGNRUxUEs4zVgaJCyd8csCEmwPsudpliLdUmyxX2pzUBojFkINpotw== + dependencies: + use-isomorphic-layout-effect "^1.0.0" + use-sync-external-store "^1.0.0" + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -8012,6 +8020,11 @@ jest@^27.5.1: import-local "^3.0.2" jest-cli "^27.5.1" +js-base64@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.2.tgz#816d11d81a8aff241603d19ce5761e13e41d7745" + integrity sha512-NnRs6dsyqUXejqk/yv2aiXlAvOs56sLkX6nUdeaNezI5LFFLlsZjOThmwnrcwh5ZZRwZlCMnVAY3CvhIhoVEKQ== + js-cookie@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" @@ -12392,6 +12405,16 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" +use-isomorphic-layout-effect@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" + integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== + +use-sync-external-store@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.0.0.tgz#d98f4a9c2e73d0f958e7e2d2c2bfb5f618cbd8fd" + integrity sha512-AFVsxg5GkFg8GDcxnl+Z0lMAz9rE8DGJCc28qnBuQF7lac57B5smLcT37aXpXIIPz75rW4g3eXHPjhHwdGskOw== + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" @@ -12878,6 +12901,11 @@ xss@1.0.10: commander "^2.20.3" cssfilter "0.0.10" +xstate@^4.31.0: + version "4.31.0" + resolved "https://registry.yarnpkg.com/xstate/-/xstate-4.31.0.tgz#039cf6f865dd9e104012eb76a14df757c988ec58" + integrity sha512-UK5m6OqUsTlPuKWkfRR5cR9/Yt7sysFyEg+PVIbEH9mwHSf9zuCvWO7rRvhBq7T+3pEXLKTEMfaqmLxl9Ob1pw== + xtend@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" diff --git a/pkg/processors/flattener/Dockerfile b/pkg/processors/flattener/Dockerfile deleted file mode 100644 index e800dbc35d..0000000000 --- a/pkg/processors/flattener/Dockerfile +++ /dev/null @@ -1,36 +0,0 @@ -FROM golang:1.14-buster AS builder - -# Add Maintainer Info -LABEL maintainer="signoz" - -ARG TARGETPLATFORM - -ENV CGO_ENABLED=0 -ENV GOPATH=/go - -RUN export GOOS=$(echo ${TARGETPLATFORM} | cut -d / -f1) && \ - export GOARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2) - -# Prepare and enter src directory -WORKDIR /go/src/github.com/signoz/signoz/pkg/processors/flattener - -# Cache dependencies -ADD go.mod . -ADD go.sum . -RUN go mod download -x - -# Add the sources and proceed with build -ADD . . -RUN go build -o ./bin/flattener ./main.go -RUN chmod +x ./bin/flattener - -# use a minimal alpine image -FROM alpine:3.7 -# add ca-certificates in case you need them -RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/* -# set working directory -WORKDIR /root -# copy the binary from builder -COPY --from=builder /go/src/github.com/signoz/signoz/pkg/processors/flattener/bin/flattener . -# run the binary -CMD ["./flattener"] \ No newline at end of file diff --git a/pkg/processors/flattener/README.md b/pkg/processors/flattener/README.md deleted file mode 100644 index aaac997e71..0000000000 --- a/pkg/processors/flattener/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Flattener Processor - -Flattener Processor is a spream processing application that reads spans from topic `otlp_spans` and writes to topic `flattened_spans`. It is written in **Golang**. Flattener Processor is responsible to convert the incoming spans into a flat model that can be ingested to Apache Druid. - - -#### Configuration -Flattener Processor needs below `env` variables to run: -``` - KAFKA_BROKER: signoz-kafka:9092 - KAFKA_INPUT_TOPIC: otlp_spans - KAFKA_OUTPUT_TOPIC: flattened_spans -``` -The above values are the default ones used by SigNoz and are kept at `deploy/kubernetes/platform/signoz-charts/flattener-processor/values.yaml` - -#### Build and Run locally -```console -cd pkg/processors/flattener -go build -o build/flattener-processor main.go -KAFKA_BROKER=xxxx KAFKA_INPUT_TOPIC=otlp_spans KAFKA_OUTPUT_TOPIC=flattened_spans build/flattener-processor -``` - -#### Docker Images -The docker images of flattener-processor is available at https://hub.docker.com/r/signoz/flattener-processor \ No newline at end of file diff --git a/pkg/processors/flattener/go.mod b/pkg/processors/flattener/go.mod deleted file mode 100644 index a47f4ad9da..0000000000 --- a/pkg/processors/flattener/go.mod +++ /dev/null @@ -1,10 +0,0 @@ -module flattener - -go 1.14 - -require ( - gitlab.com/signoz-public/goka v0.0.2 - gitlab.com/signoz-public/spanprocessor v0.0.2 - go.opentelemetry.io/collector v0.17.0 - go.uber.org/zap v1.16.0 -) diff --git a/pkg/processors/flattener/go.sum b/pkg/processors/flattener/go.sum deleted file mode 100644 index e177148c35..0000000000 --- a/pkg/processors/flattener/go.sum +++ /dev/null @@ -1,1517 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= -contrib.go.opencensus.io/exporter/prometheus v0.2.0/go.mod h1:TYmVAyE8Tn1lyPcltF5IYYfWp2KHu7lQGIZnj8iZMys= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/Azure/azure-sdk-for-go v45.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go v46.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= -github.com/Azure/go-autorest/autorest v0.11.4/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= -github.com/Azure/go-autorest/autorest v0.11.10/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= -github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= -github.com/Azure/go-autorest/autorest/adal v0.9.2/go.mod h1:/3SMAM86bP6wC9Ev35peQDUeqFZBMH07vvUOmg4z/fE= -github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= -github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= -github.com/Azure/go-autorest/autorest/validation v0.2.0/go.mod h1:3EEqHnBxQGHXRYq3HT1WyXAvT7LLY3tl70hw6tQIbjI= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= -github.com/DataDog/zstd v1.4.4/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= -github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= -github.com/HdrHistogram/hdrhistogram-go v0.9.0/go.mod h1:nxrse8/Tzg2tg3DZcZjm6qEclQKK70g0KxO61gFFZD4= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= -github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/sarama v1.22.2-0.20190604114437-cd910a683f9f/go.mod h1:XLH1GYJnLVE0XCr6KdJGVJRTwY30moWNJ4sERjXX6fs= -github.com/Shopify/sarama v1.27.0/go.mod h1:aCdj6ymI8uyPEux1JJ9gcaDT6cinjGhNCAhs54taSUo= -github.com/Shopify/sarama v1.27.2 h1:1EyY1dsxNDUQEv0O/4TsjosHI2CgB1uo9H/v56xzTxc= -github.com/Shopify/sarama v1.27.2/go.mod h1:g5s5osgELxgM+Md9Qni9rzo7Rbt+vvFQI4bt/Mc93II= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/Songmu/retry v0.1.0/go.mod h1:7sXIW7eseB9fq0FUvigRcQMVLR9tuHI0Scok+rkpAuA= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= -github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= -github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= -github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/antonmedv/expr v1.8.9/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmHhwGEk8= -github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= -github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.34.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/aws/aws-sdk-go v1.35.5/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k= -github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= -github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= -github.com/bombsimon/wsl/v3 v3.1.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= -github.com/bsm/sarama-cluster v2.1.13+incompatible/go.mod h1:r7ao+4tTNXvWm+VRpRJchr2kQhqxgmAp2iEX5W96gMM= -github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34= -github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= -github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= -github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -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.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/crossdock/crossdock-go v0.0.0-20160816171116-049aabb0122b/go.mod h1:v9FBN7gdVTpiD/+LZ7Po0UKvROyT87uLVxTHVky/dlQ= -github.com/daixiang0/gci v0.2.4/go.mod h1:+AV8KmHTGxxwp/pY84TLQfFKp2vuKXXJVzF3kD/hfR4= -github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= -github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -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/denis-tingajkin/go-header v0.3.1/go.mod h1:sq/2IxMhaZX+RRcgHfCRx/m0M5na0fBt4/CRe7Lrji0= -github.com/dgraph-io/badger v1.5.3/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= -github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= -github.com/dgraph-io/ristretto v0.0.1/go.mod h1:T40EBc7CJke8TkpiYfGGKAeFjSaxuFXhuXRyumBd6RE= -github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/dgryski/go-sip13 v0.0.0-20190329191031-25c5027a8c7b/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/dgryski/go-sip13 v0.0.0-20200911182023-62edffca9245/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/digitalocean/godo v1.42.1/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU= -github.com/digitalocean/godo v1.46.0/go.mod h1:p7dOjjtSBqCTUksqtA5Fd3uaKs9kyTq2xcz76ulEJRU= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v17.12.0-ce-rc1.0.20200706150819-a40b877fbb9e+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q= -github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21 h1:YEetp8/yCZMuEPMUDHG0CW/brkkEp8mzqk2+ODEitlw= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v0.0.0-20200808040245-162e5629780b/go.mod h1:NAJj0yf/KaRKURN6nyi7A9IZydMivZEm9oQLWNjfKDc= -github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= -github.com/frankban/quicktest v1.7.3/go.mod h1:V1d2J5pfxYH6EjBAgSK7YNXcXlTWxUHdE1sVDXkjnig= -github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y= -github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= -github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE= -github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24= -github.com/go-critic/go-critic v0.5.2/go.mod h1:cc0+HvdE3lFpqLecgqMaJcvWWH77sLdBp+wLGPM1Yyo= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= -github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= -github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= -github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= -github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ= -github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/go-openapi/errors v0.19.4/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= -github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI= -github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= -github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY= -github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= -github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= -github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= -github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= -github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= -github.com/go-openapi/spec v0.19.7/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= -github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= -github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= -github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= -github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= -github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= -github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= -github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= -github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= -github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= -github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= -github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= -github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.4.0/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= -github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= -github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= -github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= -github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= -github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= -github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= -github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= -github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= -github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= -github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= -github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= -github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= -github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= -github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= -github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= -github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= -github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= -github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= -github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= -github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= -github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= -github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= -github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= -github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= -github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= -github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gocql/gocql v0.0.0-20200228163523-cd4b606dd2fb/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY= -github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/googleapis v1.3.0/go.mod h1:d+q1s/xVJxZGKWwC/6UfPIF33J+G1Tq4GYv9Y+Tg/EU= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw= -github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= -github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= -github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= -github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= -github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= -github.com/golangci/gocyclo v0.0.0-20180528144436-0a533e8fa43d/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= -github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= -github.com/golangci/golangci-lint v1.31.0/go.mod h1:aMQuNCA+NDU5+4jLL5pEuFHoue0IznKE2+/GsFvvs8A= -github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= -github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= -github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= -github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= -github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= -github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= -github.com/google/addlicense v0.0.0-20200622132530-df58acafd6d5/go.mod h1:EMjYTRimagHs1FwlIqKyX3wAM0u3rA+McvlIIWmSamA= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201007051231-1066cbb265c7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.4.0/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= -github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= -github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gophercloud/gophercloud v0.12.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss= -github.com/gophercloud/gophercloud v0.13.0/go.mod h1:VX0Ibx85B60B5XOrZr6kaNwrmPUzcmMpwxvQ1WQIIWM= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= -github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.14.5/go.mod h1:UJ0EZAp832vCd54Wev9N1BMKEyvcZ5+IM0AwDrnlkEc= -github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= -github.com/grpc-ecosystem/grpc-gateway v1.15.0/go.mod h1:vO11I9oWA+KsxmfFQPhLnnIb1VDE24M+pdxZFiuZcA8= -github.com/grpc-ecosystem/grpc-gateway v1.15.2/go.mod h1:vO11I9oWA+KsxmfFQPhLnnIb1VDE24M+pdxZFiuZcA8= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= -github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= -github.com/hashicorp/consul/api v1.6.0/go.mod h1:1NSuaUUkFaJzMasbfq/11wKYWSR67Xn6r2DXKhuDNFg= -github.com/hashicorp/consul/api v1.7.0/go.mod h1:1NSuaUUkFaJzMasbfq/11wKYWSR67Xn6r2DXKhuDNFg= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.6.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= -github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.12.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.14.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.2.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= -github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= -github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/serf v0.9.3/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hashicorp/yamux v0.0.0-20190923154419-df201c70410d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hetznercloud/hcloud-go v1.21.1/go.mod h1:xng8lbDUg+xM1dgc0yGHX5EeqbwIq7UYlMWMTx3SQVg= -github.com/hetznercloud/hcloud-go v1.22.0/go.mod h1:xng8lbDUg+xM1dgc0yGHX5EeqbwIq7UYlMWMTx3SQVg= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/influxdata/flux v0.65.0/go.mod h1:BwN2XG2lMszOoquQaFdPET8FRQfrXiZsWmcMO9rkaVY= -github.com/influxdata/flux v0.65.1/go.mod h1:J754/zds0vvpfwuq7Gc2wRdVwEodfpCFM7mYlOw2LqY= -github.com/influxdata/influxdb v1.8.2/go.mod h1:SIzcnsjaHRFpmlxpJ4S3NT64qtEKYweNTUMb/vh0OMQ= -github.com/influxdata/influxdb v1.8.3/go.mod h1:JugdFhsvvI8gadxOI6noqNeeBHvWNTbfYGtiAn+2jhI= -github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/influxdata/influxql v1.1.0/go.mod h1:KpVI7okXjK6PRi3Z5B+mtKZli+R1DnZgb3N+tzevNgo= -github.com/influxdata/influxql v1.1.1-0.20200828144457-65d3ef77d385/go.mod h1:gHp9y86a/pxhjJ+zMjNXiQAA197Xk9wLxaz+fGG+kWk= -github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE= -github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8= -github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE= -github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0= -github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po= -github.com/jaegertracing/jaeger v1.20.0/go.mod h1:EFO94eQMRMI5KM4RIWcnl3rocmGEVt232TIG4Ua/4T0= -github.com/jaegertracing/jaeger v1.21.0/go.mod h1:PCTGGFohQBPQMR4j333V5lt6If7tj8aWJ+pQNgvZ+wU= -github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= -github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= -github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s= -github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/joshdk/go-junit v0.0.0-20200702055522-6efcf4050909/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0= -github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= -github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.0 h1:wJbzvpYMVGG9iTI9VxpnNZfd4DzMPoCWze3GgSqz8yg= -github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg= -github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/kyoh86/exportloopref v0.1.7/go.mod h1:h1rDl2Kdj97+Kwh4gdz3ujE7XHmH51Q0lUiZ1z4NLj8= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/leoluk/perflib_exporter v0.1.0/go.mod h1:rpV0lYj7lemdTm31t7zpCqYqPnw7xs86f+BaaNBVYFM= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= -github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= -github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU= -github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= -github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= -github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mjibson/esc v0.2.0/go.mod h1:9Hw9gxxfHulMF5OJKCyhYD7PzlSdhzXyaGEBRPH1OPs= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= -github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= -github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nishanths/exhaustive v0.0.0-20200811152831-6cf413ae40e0/go.mod h1:wBEpHwM2OdmeNpdCvRPUlkEbBuaFmcK4Wv8Q7FuGW3c= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olivere/elastic v6.2.27+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= -github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opentracing-contrib/go-grpc v0.0.0-20191001143057-db30781987df/go.mod h1:DYR5Eij8rJl8h7gblRrOZ8g0kW1umSpKqYIBTgeDtLo= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= -github.com/opentracing-contrib/go-stdlib v0.0.0-20190519235532-cf7a6c988dc9/go.mod h1:PLldrQSroqzH70Xl+1DQcGnefIbqsKR7UDaiux3zV+w= -github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= -github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.4-0.20200818204336-dc18516bbb4c/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= -github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= -github.com/orijtech/prometheus-go-metrics-exporter v0.0.6/go.mod h1:BiTx/ugZex8LheBk3j53tktWaRdFjV5FCfT2o0P7msE= -github.com/ory/go-acc v0.2.6/go.mod h1:4Kb/UnPcT8qRAk3IAxta+hvVapdxTLWtrr7bFLlEgpw= -github.com/ory/viper v1.7.5/go.mod h1:ypOuyJmEUb3oENywQZRgeAMwqgOyDqwboO1tj3DjTaM= -github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= -github.com/pavius/impi v0.0.3/go.mod h1:x/hU0bfdWIhuOT1SKwiJg++yvkk6EuOtJk8WtDZqgr8= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= -github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= -github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs= -github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= -github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= -github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= -github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= -github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= -github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= -github.com/pkg/term v0.0.0-20180730021639-bffc007b7fd5/go.mod h1:eCbImbZ95eXtAUIbLAuAVnBnwf83mjf6QIVH8SHYwqQ= -github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/pquerna/cachecontrol v0.0.0-20200819021114-67c6ae64274f/go.mod h1:hoLfEwdY11HjRfKFH6KqnPsfxlo3BP6bJehpDv8t6sQ= -github.com/prometheus/alertmanager v0.21.0/go.mod h1:h7tJ81NA0VLWvWEayi1QltevFkLF3KxmC/malTcT8Go= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= -github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= -github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.13.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.6/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/prometheus v1.8.2-0.20200827201422-1195cc24e3c8/go.mod h1:Td6hjwdXDmVt5CI9T03Sw+yBNxLBq/Yx3ZtmtP8zlCA= -github.com/prometheus/prometheus v1.8.2-0.20201105135750-00f16d1ac3a4/go.mod h1:XYjkJiog7fyQu3puQNivZPI2pNq1C/775EIoHfDvuvY= -github.com/prometheus/statsd_exporter v0.15.0/go.mod h1:Dv8HnkoLQkeEjkIE4/2ndAA7WL1zHKK7WMqFQqu72rw= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= -github.com/quasilyte/go-ruleguard v0.2.0/go.mod h1:2RT/tf0Ce0UDj5y243iWKosQogJd8+1G3Rs2fxmlYnw= -github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= -github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= -github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryancurrah/gomodguard v1.1.0/go.mod h1:4O8tr7hBODaGE6VIhfJDHcwzh5GUccKSJBU0UMXJFVM= -github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= -github.com/samuel/go-zookeeper v0.0.0-20200724154423-2164a8ac840e/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= -github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/sectioneight/md-to-godoc v0.0.0-20161108233149-55e43be6c335/go.mod h1:lPZq22klO8la1kyImIDhrGytugMV0TsrsZB55a+xxI0= -github.com/securego/gosec v0.0.0-20200203094520-d13bb6d2420c/go.mod h1:gp0gaHj0WlmPh9BdsTmo1aq6C27yIPWdxCKGFGdVKBE= -github.com/securego/gosec/v2 v2.4.0/go.mod h1:0/Q4cjmlFDfDUj1+Fib61sc+U5IQb2w+Iv9/C3wPVko= -github.com/segmentio/kafka-go v0.1.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= -github.com/segmentio/kafka-go v0.2.0/go.mod h1:X6itGqS9L4jDletMsxZ7Dz+JFWxM6JHfPOCvTvk+EJo= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= -github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= -github.com/shirou/gopsutil v2.20.6+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/gopsutil v3.20.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= -github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= -github.com/shurcooL/vfsgen v0.0.0-20200627165143-92b8a710ab6c/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= -github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI= -github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= -github.com/sourcegraph/go-diff v0.6.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/ssgreg/nlreturn/v2 v2.1.0/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= -github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= -github.com/tcnksm/ghr v0.13.0/go.mod h1:tcp6tzbRYE0LqFSG7ykXP/BVG1/2BkX6aIn9FFV1mIQ= -github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE= -github.com/tcnksm/go-latest v0.0.0-20170313132115-e3007ae9052e/go.mod h1:d7u6HkTYKSv5m6MCKkOQlHwaShTMl3HjqSGW3XtVhXM= -github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= -github.com/tetafro/godot v0.4.8/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= -github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= -github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= -github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk10Udg= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= -github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= -github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/uber/jaeger-client-go v2.23.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= -github.com/uber/jaeger-lib v2.4.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= -github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.15.1/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA= -github.com/valyala/quicktemplate v1.6.2/go.mod h1:mtEJpQtUiBV0SHhMX6RtiJtqxncgrfmjcUy5T68X8TM= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= -github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= -github.com/vektra/mockery v0.0.0-20181123154057-e78b021dcbb5/go.mod h1:ppEjwdhyy7Y31EnHRDm1JkChoC7LXIJ7Ex0VYLWtZtQ= -github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad/go.mod h1:Hy8o65+MXnS6EwGElrSRjUzQDLXreJlzYLlWiHtt8hM= -github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= -github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= -github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= -github.com/xlab/treeprint v1.0.0/go.mod h1:IoImgRak9i3zJyuxOKUP1v4UZd1tMoKkq/Cimt1uhCg= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -gitlab.com/signoz-public/goka v0.0.2 h1:rMeFqR0jM066y+RnFm5vEVkPh3a8Uoc3y7vaiHx6pUs= -gitlab.com/signoz-public/goka v0.0.2/go.mod h1:ASBSD93Q8y84hZZzAmhfFd/wiL56jdcJQ/R6Io6uhP8= -gitlab.com/signoz-public/spanprocessor v0.0.2 h1:uJAAM7NoZG2MwjkyM7lmn/T8DALyPVRC8USgZglWFls= -gitlab.com/signoz-public/spanprocessor v0.0.2/go.mod h1:SvMsPuLIisN8fRan7Z1PHTJCDBaFbwt57Rxt5Vc5Plo= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= -go.mongodb.org/mongo-driver v1.3.2/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opentelemetry.io v0.1.0 h1:EANZoRCOP+A3faIlw/iN6YEWoYb1vleZRKm1EvH8T48= -go.opentelemetry.io/collector v0.14.0/go.mod h1:itblxiZ5r454TNNQVvcAp7vj7LbwCdeNRtodo2t+lGM= -go.opentelemetry.io/collector v0.17.0 h1:Da/hGGexGLFjp444zMJ3y28y0R9Ll/kJTfNVpS2pliY= -go.opentelemetry.io/collector v0.17.0/go.mod h1:h9AuSwXGdKT5M+avYsM8KGL7FiObqUL/1s+BBLpLB80= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/automaxprocs v1.3.0/go.mod h1:9CWT6lKIep8U41DDaPiH6eFscnTyjfTANNQNx6LrIcA= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= -go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190921015927-1a5e07d1ff72/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200528225125-3c3fba18258b/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200930132711-30421366ff76/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200821140526-fda516888d29/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201008064518-c1f3e3309c71/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181112210238-4b1f3b6b1646/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191203134012-c197fd4bf371/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200203023011-6f24f261dadb/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200321224714-0d839f3cf2ed/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200513201620-d5fe73897c97/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200603131246-cc40288be839/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200701041122-1837592efa10/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200822203824-307de81be3f4/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201008025239-9df69603baec/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= -golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= -gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200624020401-64a14ca9d1ad/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d h1:92D1fum1bJLKSdr11OJ+54YeCMCGYIygTA7R/YZxH5M= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc/examples v0.0.0-20200728065043-dfc0c05b2da9/go.mod h1:5j1uub0jRGhRiSghIlrThmBUgcgLXOVJQ/l1getT4uo= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= -gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw= -gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= -gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM= -gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= -gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= -gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= -gopkg.in/jcmturner/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlIrg= -gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= -gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU= -gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= -gopkg.in/redis.v5 v5.2.9/go.mod h1:6gtv0/+A4iM08kdRfocWYB3bLX2tebpNtfKlFT6H4mY= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200601152816-913338de1bd2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.5/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzEYLhQB2YY= -k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY= -k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI= -k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig= -k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= -k8s.io/client-go v0.18.8/go.mod h1:HqFqMllQ5NnQJNwjro9k5zMyfhZlOwpuTLVrxjkYSxU= -k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA= -k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.3.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= -k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= -k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20200414100711-2df71ebbae66/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -mvdan.cc/gofumpt v0.0.0-20200709182408-4fd085cb6d5f/go.mod h1:9VQ397fNXEnF84t90W4r4TRCQK+pg9f8ugVfyj+S26w= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= -mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= -sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/pkg/processors/flattener/main.go b/pkg/processors/flattener/main.go deleted file mode 100644 index 8d9956caed..0000000000 --- a/pkg/processors/flattener/main.go +++ /dev/null @@ -1,366 +0,0 @@ -package main - -import ( - "context" - "encoding/hex" - "encoding/json" - "fmt" - "os" - "strconv" - - "gitlab.com/signoz-public/goka" - sp "gitlab.com/signoz-public/spanprocessor" - "gitlab.com/signoz-public/spanprocessor/consumer/pdata" - "go.opentelemetry.io/collector/translator/conventions" - "go.uber.org/zap" -) - -/* - -Some error message when processor is kept for running for long duration - - 1. Restoring from local storage on application restart - 2. goka best practices - 3. Every persist call writes to kafka topic otel-collector-table with increasing number of spans - 4. In Goka a single processor cannot work with 2 stores? What if I need 2 tables? - 5. Goka does not have Punctuator analogy - 6. The key is used to find the partition. Why do we have just 1 partition? - - Notes: - 1. goka.Context is changed to have setKey func and correctly read key from []byte using hex.EncodeToString - 2. What to do if we receive a span after TIMEOUT_INTERVAL? -> no logic has been implemented - 3. go-routines started are never cleared after Done() - 4. Delete key from table -> gives error - - -*/ - -var ( - brokers = []string{os.Getenv("KAFKA_BROKER")} - INPUT_TOPIC goka.Stream = goka.Stream(os.Getenv("KAFKA_INPUT_TOPIC")) - OUTPUT_TOPIC goka.Stream = goka.Stream(os.Getenv("KAFKA_OUTPUT_TOPIC")) - group goka.Group = "flattener" -) - -type Span struct { - TraceId string - SpanId string - ParentSpanId string - Name string - DurationNano uint64 - StartTimeUnixNano uint64 - ServiceName string - Kind int32 - References []OtelSpanRef - Tags []string - TagsKeys []string - TagsValues []string - StatusCode int64 - ExternalHttpMethod string - ExternalHttpUrl string - Component string - DBSystem string - DBName string - DBOperation string - PeerService string -} - -type OtelSpanRef struct { - TraceId string - SpanId string - RefType string -} - -var unmarshaller = sp.DefaultUnmarshallers()["otlp_proto"] -var marshaller = sp.DefaultMarshallers()["otlp_proto"] - -type OtelCodec struct{} -type OutputCodec struct{} - -func (c *OutputCodec) Encode(value interface{}) ([]byte, error) { - // fmt.Println("Encoding to LevelDB ...") - - // st := value.(*StructuredTrace) - // for _, span := range st.spans { - // fmt.Println(span.Name) - // } - - b, err := json.Marshal(value) - // os.Stderr.Write(b) - return b, err -} - -func (c *OutputCodec) Decode(data []byte) (interface{}, error) { - // fmt.Println("Decoding from LevelDB ...") - var ss Span - err := json.Unmarshal(data, &ss) - return &ss, err -} - -func (c *OtelCodec) Encode(value interface{}) ([]byte, error) { - // fmt.Println("Encoding Otel Traces ...") - // messages, err = marshaller.Marshal(td) - // return messages, err - return json.Marshal(value) -} - -func (c *OtelCodec) Decode(data []byte) (interface{}, error) { - // fmt.Println("Decoding Otel Traces ...") - traces, err := unmarshaller.Unmarshal(data) - // printTraces(&traces) - return &traces, err -} - -func makeJaegerProtoReferences( - links pdata.SpanLinkSlice, - parentSpanID pdata.SpanID, - traceID pdata.TraceID, -) ([]OtelSpanRef, error) { - - parentSpanIDSet := len(parentSpanID.Bytes()) != 0 - if !parentSpanIDSet && links.Len() == 0 { - return nil, nil - } - - refsCount := links.Len() - if parentSpanIDSet { - refsCount++ - } - - refs := make([]OtelSpanRef, 0, refsCount) - - // Put parent span ID at the first place because usually backends look for it - // as the first CHILD_OF item in the model.SpanRef slice. - if parentSpanIDSet { - - refs = append(refs, OtelSpanRef{ - TraceId: traceID.HexString(), - SpanId: parentSpanID.HexString(), - RefType: "CHILD_OF", - }) - } - - for i := 0; i < links.Len(); i++ { - link := links.At(i) - if link.IsNil() { - continue - } - - refs = append(refs, OtelSpanRef{ - TraceId: link.TraceID().HexString(), - SpanId: link.SpanID().HexString(), - - // Since Jaeger RefType is not captured in internal data, - // use SpanRefType_FOLLOWS_FROM by default. - // SpanRefType_CHILD_OF supposed to be set only from parentSpanID. - RefType: "FOLLOWS_FROM", - }) - } - - return refs, nil -} - -func printTraces(traces_pt *pdata.Traces) { - traces := *traces_pt - rss := traces.ResourceSpans() - for i := 0; i < rss.Len(); i++ { - fmt.Printf("ResourceSpans #%d\n", i) - rs := rss.At(i) - if rs.IsNil() { - fmt.Println("* Nil ResourceSpans") - continue - } - ilss := rs.InstrumentationLibrarySpans() - for j := 0; j < ilss.Len(); j++ { - fmt.Printf("InstrumentationLibrarySpans #%d\n", j) - ils := ilss.At(j) - if ils.IsNil() { - fmt.Printf("* Nil InstrumentationLibrarySpans\n") - continue - } - - spans := ils.Spans() - for k := 0; k < spans.Len(); k++ { - span := spans.At(k) - // references := span.Links() - jReferences, _ := makeJaegerProtoReferences(span.Links(), span.ParentSpanID(), span.TraceID()) - for _, ref := range jReferences { - fmt.Println(ref.TraceId) - fmt.Println(ref.SpanId) - fmt.Println(ref.RefType) - } - // traceID := hex.EncodeToString(span.TraceID()) - traceID_bytes := span.TraceID().Bytes() - traceID := hex.EncodeToString(traceID_bytes[:]) - fmt.Println(traceID) - // os.Exit(3) - } - } - } -} - -func byteSlice2string(byteSlice []byte) string { - return hex.EncodeToString(byteSlice) -} - -func populateOtherDimensions(attributes pdata.AttributeMap, span *Span) { - - attributes.ForEach(func(k string, v pdata.AttributeValue) { - if k == "http.status_code" { - span.StatusCode = v.IntVal() - } - if k == "http.url" { - span.ExternalHttpUrl = v.StringVal() - } - if k == "http.method" { - span.ExternalHttpMethod = v.StringVal() - } - if k == "component" { - span.Component = v.StringVal() - } - - if k == "db.system" { - span.DBSystem = v.StringVal() - } - if k == "db.name" { - span.DBName = v.StringVal() - } - if k == "db.operation" { - span.DBOperation = v.StringVal() - } - if k == "peer.service" { - span.PeerService = v.StringVal() - } - - }) - -} - -func newStructuredSpan(otelSpan pdata.Span, ServiceName string) *Span { - - durationNano := uint64(otelSpan.EndTime() - otelSpan.StartTime()) - traceID_bytes := otelSpan.TraceID().Bytes() - spanID_bytes := otelSpan.SpanID().Bytes() - parentSpanID_bytes := otelSpan.ParentSpanID().Bytes() - - attributes := otelSpan.Attributes() - - var tags []string - var tagsKeys []string - var tagsValues []string - var tag string - - attributes.ForEach(func(k string, v pdata.AttributeValue) { - if v.Type().String() == "INT" { - tag = fmt.Sprintf("%s:%d", k, v.IntVal()) - tagsValues = append(tagsValues, strconv.FormatInt(v.IntVal(), 10)) - } else { - tag = fmt.Sprintf("%s:%s", k, v.StringVal()) - tagsValues = append(tagsValues, v.StringVal()) - } - - tags = append(tags, tag) - tagsKeys = append(tagsKeys, k) - - }) - - references, _ := makeJaegerProtoReferences(otelSpan.Links(), otelSpan.ParentSpanID(), otelSpan.TraceID()) - - var span *Span = &Span{ - TraceId: hex.EncodeToString(traceID_bytes[:]), - SpanId: hex.EncodeToString(spanID_bytes[:]), - ParentSpanId: hex.EncodeToString(parentSpanID_bytes[:]), - Name: otelSpan.Name(), - StartTimeUnixNano: uint64(otelSpan.StartTime()), - DurationNano: durationNano, - ServiceName: ServiceName, - Kind: int32(otelSpan.Kind()), - References: references, - Tags: tags, - TagsKeys: tagsKeys, - TagsValues: tagsValues, - } - span.StatusCode = int64(otelSpan.Status().Code()) - - populateOtherDimensions(attributes, span) - - return span -} - -// ServiceNameForResource gets the service name for a specified Resource. -// TODO: Find a better package for this function. -func ServiceNameForResource(resource pdata.Resource) string { - // if resource.IsNil() { - // return "" - // } - - service, found := resource.Attributes().Get(conventions.AttributeServiceName) - if !found { - return "" - } - - return service.StringVal() -} - -func process(ctx goka.Context, msg interface{}) { - - traces_ptr := msg.(*pdata.Traces) - - // printTraces(traces_ptr) - - rss := (*traces_ptr).ResourceSpans() - for i := 0; i < rss.Len(); i++ { - // fmt.Printf("ResourceSpans #%d\n", i) - rs := rss.At(i) - if rs.IsNil() { - fmt.Println("* Nil ResourceSpans") - continue - } - - serviceName := ServiceNameForResource(rs.Resource()) - - ilss := rs.InstrumentationLibrarySpans() - for j := 0; j < ilss.Len(); j++ { - // fmt.Printf("InstrumentationLibrarySpans #%d\n", j) - ils := ilss.At(j) - if ils.IsNil() { - fmt.Printf("* Nil InstrumentationLibrarySpans\n") - continue - } - - spans := ils.Spans() - - for k := 0; k < spans.Len(); k++ { - span := spans.At(k) - // traceID := hex.EncodeToString(span.TraceID()) - structuredSpan := newStructuredSpan(span, serviceName) - ctx.Emit(OUTPUT_TOPIC, structuredSpan.TraceId, structuredSpan) - } - } - } - -} - -func runProcessor() { - - g := goka.DefineGroup(group, - goka.Input(INPUT_TOPIC, new(OtelCodec), process), - goka.Output(OUTPUT_TOPIC, new(OutputCodec)), - ) - - p, err := goka.NewProcessor(brokers, g) - if err != nil { - zap.S().Error(err) - panic(err) - } - - zap.S().Info("Running flattener processor ...") - - p.Run(context.Background()) - -} - -func main() { - runProcessor() - -} diff --git a/pkg/query-service/Dockerfile b/pkg/query-service/Dockerfile index fe93291f79..3689b2bf23 100644 --- a/pkg/query-service/Dockerfile +++ b/pkg/query-service/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.14-buster AS builder +FROM golang:1.17-buster AS builder # LD_FLAGS is passed as argument from Makefile. It will be empty, if no argument passed ARG LD_FLAGS diff --git a/pkg/query-service/app/clickhouseReader/options.go b/pkg/query-service/app/clickhouseReader/options.go index 66a1eaacdc..30f23b5cf3 100644 --- a/pkg/query-service/app/clickhouseReader/options.go +++ b/pkg/query-service/app/clickhouseReader/options.go @@ -1,9 +1,11 @@ package clickhouseReader import ( + "context" + "net/url" "time" - "github.com/jmoiron/sqlx" + "github.com/ClickHouse/clickhouse-go/v2" ) type Encoding string @@ -16,13 +18,16 @@ const ( ) const ( - defaultDatasource string = "tcp://localhost:9000" - defaultOperationsTable string = "signoz_operations" - defaultIndexTable string = "signoz_index" - defaultErrorTable string = "signoz_error_index" - defaultWriteBatchDelay time.Duration = 5 * time.Second - defaultWriteBatchSize int = 10000 - defaultEncoding Encoding = EncodingJSON + defaultDatasource string = "tcp://localhost:9000" + defaultTraceDB string = "signoz_traces" + defaultOperationsTable string = "signoz_operations" + defaultIndexTable string = "signoz_index_v2" + defaultErrorTable string = "signoz_error_index" + defaulDurationTable string = "durationSortMV" + defaultSpansTable string = "signoz_spans" + defaultWriteBatchDelay time.Duration = 5 * time.Second + defaultWriteBatchSize int = 10000 + defaultEncoding Encoding = EncodingJSON ) const ( @@ -41,8 +46,10 @@ type namespaceConfig struct { namespace string Enabled bool Datasource string + TraceDB string OperationsTable string IndexTable string + DurationTable string SpansTable string ErrorTable string WriteBatchDelay time.Duration @@ -52,15 +59,27 @@ type namespaceConfig struct { } // Connecto defines how to connect to the database -type Connector func(cfg *namespaceConfig) (*sqlx.DB, error) +type Connector func(cfg *namespaceConfig) (clickhouse.Conn, error) -func defaultConnector(cfg *namespaceConfig) (*sqlx.DB, error) { - db, err := sqlx.Open("clickhouse", cfg.Datasource) +func defaultConnector(cfg *namespaceConfig) (clickhouse.Conn, error) { + ctx := context.Background() + dsnURL, err := url.Parse(cfg.Datasource) + options := &clickhouse.Options{ + Addr: []string{dsnURL.Host}, + } + if dsnURL.Query().Get("username") != "" { + auth := clickhouse.Auth{ + Username: dsnURL.Query().Get("username"), + Password: dsnURL.Query().Get("password"), + } + options.Auth = auth + } + db, err := clickhouse.Open(options) if err != nil { return nil, err } - if err := db.Ping(); err != nil { + if err := db.Ping(ctx); err != nil { return nil, err } @@ -86,9 +105,12 @@ func NewOptions(datasource string, primaryNamespace string, otherNamespaces ...s namespace: primaryNamespace, Enabled: true, Datasource: datasource, + TraceDB: defaultTraceDB, OperationsTable: defaultOperationsTable, IndexTable: defaultIndexTable, ErrorTable: defaultErrorTable, + DurationTable: defaulDurationTable, + SpansTable: defaultSpansTable, WriteBatchDelay: defaultWriteBatchDelay, WriteBatchSize: defaultWriteBatchSize, Encoding: defaultEncoding, @@ -102,6 +124,7 @@ func NewOptions(datasource string, primaryNamespace string, otherNamespaces ...s options.others[namespace] = &namespaceConfig{ namespace: namespace, Datasource: datasource, + TraceDB: "", OperationsTable: "", IndexTable: "", ErrorTable: "", diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go index de49aa0e49..c5c437dd33 100644 --- a/pkg/query-service/app/clickhouseReader/reader.go +++ b/pkg/query-service/app/clickhouseReader/reader.go @@ -9,6 +9,7 @@ import ( "flag" "fmt" "io/ioutil" + "math/rand" "net" "net/http" "net/url" @@ -20,30 +21,30 @@ import ( "sync" "time" - sd_config "github.com/prometheus/prometheus/discovery/config" - "github.com/prometheus/prometheus/scrape" - - "github.com/pkg/errors" - - _ "github.com/ClickHouse/clickhouse-go" "github.com/go-kit/log" "github.com/go-kit/log/level" - "github.com/jmoiron/sqlx" "github.com/oklog/oklog/pkg/group" + "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" - promModel "github.com/prometheus/common/model" "github.com/prometheus/common/promlog" "github.com/prometheus/prometheus/config" "github.com/prometheus/prometheus/discovery" + sd_config "github.com/prometheus/prometheus/discovery/config" "github.com/prometheus/prometheus/notifier" "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/rules" + "github.com/prometheus/prometheus/scrape" "github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/storage/remote" - "github.com/prometheus/prometheus/storage/tsdb" "github.com/prometheus/prometheus/util/stats" "github.com/prometheus/prometheus/util/strutil" + "github.com/prometheus/tsdb" + "github.com/ClickHouse/clickhouse-go/v2" + "github.com/ClickHouse/clickhouse-go/v2/lib/driver" + "github.com/jmoiron/sqlx" + + promModel "github.com/prometheus/common/model" "go.signoz.io/query-service/constants" am "go.signoz.io/query-service/integrations/alertManager" "go.signoz.io/query-service/model" @@ -51,27 +52,44 @@ import ( ) const ( - primaryNamespace = "clickhouse" - archiveNamespace = "clickhouse-archive" - signozTraceTableName = "signoz_index" - signozMetricDBName = "signoz_metrics" - signozSampleName = "samples" - signozTSName = "time_series" + primaryNamespace = "clickhouse" + archiveNamespace = "clickhouse-archive" + signozTraceDBName = "signoz_traces" + signozDurationMVTable = "durationSort" + signozSpansTable = "signoz_spans" + signozErrorIndexTable = "signoz_error_index" + signozTraceTableName = "signoz_index_v2" + signozMetricDBName = "signoz_metrics" + signozSampleName = "samples" + signozTSName = "time_series" + signozSampleTableName = "samples" + signozTSTableName = "time_series" + + minTimespanForProgressiveSearch = time.Hour + minTimespanForProgressiveSearchMargin = time.Minute + maxProgressiveSteps = 4 + charset = "abcdefghijklmnopqrstuvwxyz" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ) var ( - ErrNoOperationsTable = errors.New("no operations table supplied") - ErrNoIndexTable = errors.New("no index table supplied") - ErrStartTimeRequired = errors.New("start time is required for search queries") + ErrNoOperationsTable = errors.New("no operations table supplied") + ErrNoIndexTable = errors.New("no index table supplied") + ErrStartTimeRequired = errors.New("start time is required for search queries") + seededRand *rand.Rand = rand.New( + rand.NewSource(time.Now().UnixNano())) ) // SpanWriter for reading spans from ClickHouse type ClickHouseReader struct { - db *sqlx.DB + db clickhouse.Conn localDB *sqlx.DB + traceDB string operationsTable string + durationTable string indexTable string errorTable string + spansTable string queryEngine *promql.Engine remoteStorage *remote.Storage ruleManager *rules.Manager @@ -96,10 +114,13 @@ func NewReader(localDB *sqlx.DB) *ClickHouseReader { return &ClickHouseReader{ db: db, localDB: localDB, + traceDB: options.primary.TraceDB, alertManager: alertManager, operationsTable: options.primary.OperationsTable, indexTable: options.primary.IndexTable, errorTable: options.primary.ErrorTable, + durationTable: options.primary.DurationTable, + spansTable: options.primary.SpansTable, } } @@ -509,7 +530,7 @@ func sendAlerts(n *notifier.Manager, externalURL string) rules.NotifyFunc { } } -func initialize(options *Options) (*sqlx.DB, error) { +func initialize(options *Options) (clickhouse.Conn, error) { db, err := connect(options.getPrimary()) if err != nil { @@ -519,7 +540,7 @@ func initialize(options *Options) (*sqlx.DB, error) { return db, nil } -func connect(cfg *namespaceConfig) (*sqlx.DB, error) { +func connect(cfg *namespaceConfig) (clickhouse.Conn, error) { if cfg.Encoding != EncodingJSON && cfg.Encoding != EncodingProto { return nil, fmt.Errorf("unknown encoding %q, supported: %q, %q", cfg.Encoding, EncodingJSON, EncodingProto) } @@ -671,7 +692,7 @@ func (r *ClickHouseReader) LoadChannel(channel *model.ChannelItem) *model.ApiErr if response.StatusCode > 299 { responseData, _ := ioutil.ReadAll(response.Body) - err := fmt.Errorf("Error in getting 2xx response in API call to alertmanager/v1/receivers\n", response.Status, string(responseData)) + err := fmt.Errorf("Error in getting 2xx response in API call to alertmanager/v1/receivers\n Status: %s \n Data: %s", response.Status, string(responseData)) zap.S().Error(err) return &model.ApiError{Typ: model.ErrorInternal, Err: err} @@ -689,6 +710,8 @@ func (r *ClickHouseReader) GetChannel(id string) (*model.ChannelItem, *model.Api err := r.localDB.Get(&channel, query) + zap.S().Info(query) + if err != nil { zap.S().Debug("Error in processing sql query: ", err) return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} @@ -737,7 +760,7 @@ func (r *ClickHouseReader) DeleteChannel(id string) *model.ApiError { err = tx.Commit() if err != nil { - zap.S().Errorf("Error in commiting transaction for DELETE command to notification_channels\n", err) + zap.S().Errorf("Error in committing transaction for DELETE command to notification_channels\n", err) return &model.ApiError{Typ: model.ErrorInternal, Err: err} } @@ -753,7 +776,7 @@ func (r *ClickHouseReader) GetChannels() (*[]model.ChannelItem, *model.ApiError) err := r.localDB.Select(&channels, query) - // zap.S().Info(query) + zap.S().Info(query) if err != nil { zap.S().Debug("Error in processing sql query: ", err) @@ -843,7 +866,7 @@ func (r *ClickHouseReader) EditChannel(receiver *am.Receiver, id string) (*am.Re err = tx.Commit() if err != nil { - zap.S().Errorf("Error in commiting transaction for INSERT to notification_channels\n", err) + zap.S().Errorf("Error in committing transaction for INSERT to notification_channels\n", err) return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} } @@ -887,7 +910,7 @@ func (r *ClickHouseReader) CreateChannel(receiver *am.Receiver) (*am.Receiver, * err = tx.Commit() if err != nil { - zap.S().Errorf("Error in commiting transaction for INSERT to notification_channels\n", err) + zap.S().Errorf("Error in committing transaction for INSERT to notification_channels\n", err) return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} } @@ -932,7 +955,7 @@ func (r *ClickHouseReader) CreateRule(rule string) *model.ApiError { } err = tx.Commit() if err != nil { - zap.S().Errorf("Error in commiting transaction for INSERT to rules\n", err) + zap.S().Errorf("Error in committing transaction for INSERT to rules\n", err) return &model.ApiError{Typ: model.ErrorInternal, Err: err} } return nil @@ -974,7 +997,7 @@ func (r *ClickHouseReader) EditRule(rule string, id string) *model.ApiError { err = tx.Commit() if err != nil { - zap.S().Errorf("Error in commiting transaction for UPDATE to rules\n", err) + zap.S().Errorf("Error in committing transaction for UPDATE to rules\n", err) return &model.ApiError{Typ: model.ErrorInternal, Err: err} } @@ -1020,7 +1043,7 @@ func (r *ClickHouseReader) DeleteRule(id string) *model.ApiError { err = tx.Commit() if err != nil { - zap.S().Errorf("Error in commiting transaction for deleting rules\n", err) + zap.S().Errorf("Error in committing transaction for deleting rules\n", err) return &model.ApiError{Typ: model.ErrorInternal, Err: err} } @@ -1030,7 +1053,7 @@ func (r *ClickHouseReader) DeleteRule(id string) *model.ApiError { func (r *ClickHouseReader) GetInstantQueryMetricsResult(ctx context.Context, queryParams *model.InstantQueryMetricsParams) (*promql.Result, *stats.QueryStats, *model.ApiError) { qry, err := r.queryEngine.NewInstantQuery(r.remoteStorage, queryParams.Query, queryParams.Time) if err != nil { - return nil, nil, &model.ApiError{model.ErrorBadData, err} + return nil, nil, &model.ApiError{Typ: model.ErrorBadData, Err: err} } res := qry.Exec(ctx) @@ -1051,7 +1074,7 @@ func (r *ClickHouseReader) GetQueryRangeResult(ctx context.Context, query *model qry, err := r.queryEngine.NewRangeQuery(r.remoteStorage, query.Query, query.Start, query.End, query.Step) if err != nil { - return nil, nil, &model.ApiError{model.ErrorBadData, err} + return nil, nil, &model.ApiError{Typ: model.ErrorBadData, Err: err} } res := qry.Exec(ctx) @@ -1066,17 +1089,12 @@ func (r *ClickHouseReader) GetQueryRangeResult(ctx context.Context, query *model return res, qs, nil } -func (r *ClickHouseReader) GetServices(ctx context.Context, queryParams *model.GetServicesParams) (*[]model.ServiceItem, error) { +func (r *ClickHouseReader) GetServicesList(ctx context.Context) (*[]string, error) { - if r.indexTable == "" { - return nil, ErrNoIndexTable - } + services := []string{} + query := fmt.Sprintf(`SELECT DISTINCT serviceName FROM %s.%s WHERE toDate(timestamp) > now() - INTERVAL 1 DAY`, r.traceDB, r.indexTable) - serviceItems := []model.ServiceItem{} - - query := fmt.Sprintf("SELECT serviceName, quantile(0.99)(durationNano) as p99, avg(durationNano) as avgDuration, count(*) as numCalls FROM %s WHERE timestamp>='%s' AND timestamp<='%s' AND kind='2' GROUP BY serviceName ORDER BY p99 DESC", r.indexTable, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)) - - err := r.db.Select(&serviceItems, query) + rows, err := r.db.Query(ctx, query) zap.S().Info(query) @@ -1085,23 +1103,63 @@ func (r *ClickHouseReader) GetServices(ctx context.Context, queryParams *model.G return nil, fmt.Errorf("Error in processing sql query") } + defer rows.Close() + for rows.Next() { + var serviceName string + if err := rows.Scan(&serviceName); err != nil { + return &services, err + } + services = append(services, serviceName) + } + return &services, nil +} + +func (r *ClickHouseReader) GetServices(ctx context.Context, queryParams *model.GetServicesParams) (*[]model.ServiceItem, *model.ApiError) { + + if r.indexTable == "" { + return nil, &model.ApiError{Typ: model.ErrorExec, Err: ErrNoIndexTable} + } + + serviceItems := []model.ServiceItem{} + + query := fmt.Sprintf("SELECT serviceName, quantile(0.99)(durationNano) as p99, avg(durationNano) as avgDuration, count(*) as numCalls FROM %s.%s WHERE timestamp>='%s' AND timestamp<='%s' AND kind='2'", r.traceDB, r.indexTable, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)) + args := []interface{}{} + args, errStatus := buildQueryWithTagParams(ctx, queryParams.Tags, &query, args) + if errStatus != nil { + return nil, errStatus + } + query += " GROUP BY serviceName ORDER BY p99 DESC" + err := r.db.Select(ctx, &serviceItems, query, args...) + + zap.S().Info(query) + + if err != nil { + zap.S().Debug("Error in processing sql query: ", err) + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query")} + } + ////////////////// Below block gets 5xx of services serviceErrorItems := []model.ServiceItem{} - query = fmt.Sprintf("SELECT serviceName, count(*) as numErrors FROM %s WHERE timestamp>='%s' AND timestamp<='%s' AND kind='2' AND (statusCode>=500 OR statusCode=2) GROUP BY serviceName", r.indexTable, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)) - - err = r.db.Select(&serviceErrorItems, query) + query = fmt.Sprintf("SELECT serviceName, count(*) as numErrors FROM %s.%s WHERE timestamp>='%s' AND timestamp<='%s' AND kind='2' AND (statusCode>=500 OR statusCode=2)", r.traceDB, r.indexTable, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)) + args = []interface{}{} + args, errStatus = buildQueryWithTagParams(ctx, queryParams.Tags, &query, args) + if errStatus != nil { + return nil, errStatus + } + query += " GROUP BY serviceName" + err = r.db.Select(ctx, &serviceErrorItems, query, args...) zap.S().Info(query) if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, fmt.Errorf("Error in processing sql query") + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query")} } - m5xx := make(map[string]int) + m5xx := make(map[string]uint64) - for j, _ := range serviceErrorItems { + for j := range serviceErrorItems { m5xx[serviceErrorItems[j].ServiceName] = serviceErrorItems[j].NumErrors } /////////////////////////////////////////// @@ -1110,197 +1168,121 @@ func (r *ClickHouseReader) GetServices(ctx context.Context, queryParams *model.G service4xxItems := []model.ServiceItem{} - query = fmt.Sprintf("SELECT serviceName, count(*) as num4xx FROM %s WHERE timestamp>='%s' AND timestamp<='%s' AND kind='2' AND statusCode>=400 AND statusCode<500 GROUP BY serviceName", r.indexTable, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)) - - err = r.db.Select(&service4xxItems, query) + query = fmt.Sprintf("SELECT serviceName, count(*) as num4xx FROM %s.%s WHERE timestamp>='%s' AND timestamp<='%s' AND kind='2' AND statusCode>=400 AND statusCode<500", r.traceDB, r.indexTable, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)) + args = []interface{}{} + args, errStatus = buildQueryWithTagParams(ctx, queryParams.Tags, &query, args) + if errStatus != nil { + return nil, errStatus + } + query += " GROUP BY serviceName" + err = r.db.Select(ctx, &service4xxItems, query, args...) zap.S().Info(query) if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, fmt.Errorf("Error in processing sql query") + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query")} } - m4xx := make(map[string]int) + m4xx := make(map[string]uint64) - for j, _ := range service4xxItems { + for j := range service4xxItems { m5xx[service4xxItems[j].ServiceName] = service4xxItems[j].Num4XX } - for i, _ := range serviceItems { + for i := range serviceItems { if val, ok := m5xx[serviceItems[i].ServiceName]; ok { serviceItems[i].NumErrors = val } if val, ok := m4xx[serviceItems[i].ServiceName]; ok { serviceItems[i].Num4XX = val } - serviceItems[i].CallRate = float32(serviceItems[i].NumCalls) / float32(queryParams.Period) - serviceItems[i].FourXXRate = float32(serviceItems[i].Num4XX) / float32(queryParams.Period) - serviceItems[i].ErrorRate = float32(serviceItems[i].NumErrors) / float32(queryParams.Period) + serviceItems[i].CallRate = float64(serviceItems[i].NumCalls) / float64(queryParams.Period) + serviceItems[i].FourXXRate = float64(serviceItems[i].Num4XX) / float64(queryParams.Period) + serviceItems[i].ErrorRate = float64(serviceItems[i].NumErrors) / float64(queryParams.Period) } return &serviceItems, nil } -func (r *ClickHouseReader) GetServiceOverview(ctx context.Context, queryParams *model.GetServiceOverviewParams) (*[]model.ServiceOverviewItem, error) { +func (r *ClickHouseReader) GetServiceOverview(ctx context.Context, queryParams *model.GetServiceOverviewParams) (*[]model.ServiceOverviewItem, *model.ApiError) { serviceOverviewItems := []model.ServiceOverviewItem{} - query := fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %s minute) as time, quantile(0.99)(durationNano) as p99, quantile(0.95)(durationNano) as p95,quantile(0.50)(durationNano) as p50, count(*) as numCalls FROM %s WHERE timestamp>='%s' AND timestamp<='%s' AND kind='2' AND serviceName='%s' GROUP BY time ORDER BY time DESC", strconv.Itoa(int(queryParams.StepSeconds/60)), r.indexTable, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10), queryParams.ServiceName) - - err := r.db.Select(&serviceOverviewItems, query) + query := fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %s minute) as time, quantile(0.99)(durationNano) as p99, quantile(0.95)(durationNano) as p95,quantile(0.50)(durationNano) as p50, count(*) as numCalls FROM %s.%s WHERE timestamp>='%s' AND timestamp<='%s' AND kind='2' AND serviceName='%s'", strconv.Itoa(int(queryParams.StepSeconds/60)), r.traceDB, r.indexTable, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10), queryParams.ServiceName) + args := []interface{}{} + args, errStatus := buildQueryWithTagParams(ctx, queryParams.Tags, &query, args) + if errStatus != nil { + return nil, errStatus + } + query += " GROUP BY time ORDER BY time DESC" + err := r.db.Select(ctx, &serviceOverviewItems, query, args...) zap.S().Info(query) if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, fmt.Errorf("Error in processing sql query") + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query")} } serviceErrorItems := []model.ServiceErrorItem{} - query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %s minute) as time, count(*) as numErrors FROM %s WHERE timestamp>='%s' AND timestamp<='%s' AND kind='2' AND serviceName='%s' AND (statusCode>=500 OR statusCode=2) GROUP BY time ORDER BY time DESC", strconv.Itoa(int(queryParams.StepSeconds/60)), r.indexTable, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10), queryParams.ServiceName) - - err = r.db.Select(&serviceErrorItems, query) + query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %s minute) as time, count(*) as numErrors FROM %s.%s WHERE timestamp>='%s' AND timestamp<='%s' AND kind='2' AND serviceName='%s' AND hasError=true", strconv.Itoa(int(queryParams.StepSeconds/60)), r.traceDB, r.indexTable, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10), queryParams.ServiceName) + args = []interface{}{} + args, errStatus = buildQueryWithTagParams(ctx, queryParams.Tags, &query, args) + if errStatus != nil { + return nil, errStatus + } + query += " GROUP BY time ORDER BY time DESC" + err = r.db.Select(ctx, &serviceErrorItems, query, args...) zap.S().Info(query) if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, fmt.Errorf("Error in processing sql query") + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query")} } m := make(map[int64]int) - for j, _ := range serviceErrorItems { - timeObj, _ := time.Parse(time.RFC3339Nano, serviceErrorItems[j].Time) - m[int64(timeObj.UnixNano())] = serviceErrorItems[j].NumErrors + for j := range serviceErrorItems { + m[int64(serviceErrorItems[j].Time.UnixNano())] = int(serviceErrorItems[j].NumErrors) } - for i, _ := range serviceOverviewItems { - timeObj, _ := time.Parse(time.RFC3339Nano, serviceOverviewItems[i].Time) - serviceOverviewItems[i].Timestamp = int64(timeObj.UnixNano()) - serviceOverviewItems[i].Time = "" + for i := range serviceOverviewItems { + serviceOverviewItems[i].Timestamp = int64(serviceOverviewItems[i].Time.UnixNano()) if val, ok := m[serviceOverviewItems[i].Timestamp]; ok { - serviceOverviewItems[i].NumErrors = val + serviceOverviewItems[i].NumErrors = uint64(val) } - serviceOverviewItems[i].ErrorRate = float32(serviceOverviewItems[i].NumErrors) * 100 / float32(serviceOverviewItems[i].NumCalls) - serviceOverviewItems[i].CallRate = float32(serviceOverviewItems[i].NumCalls) / float32(queryParams.StepSeconds) + serviceOverviewItems[i].ErrorRate = float64(serviceOverviewItems[i].NumErrors) * 100 / float64(serviceOverviewItems[i].NumCalls) + serviceOverviewItems[i].CallRate = float64(serviceOverviewItems[i].NumCalls) / float64(queryParams.StepSeconds) } return &serviceOverviewItems, nil - -} - -func (r *ClickHouseReader) SearchSpans(ctx context.Context, queryParams *model.SpanSearchParams) (*[]model.SearchSpansResult, error) { - - query := fmt.Sprintf("SELECT timestamp, spanID, traceID, serviceName, name, kind, durationNano, tagsKeys, tagsValues FROM %s WHERE timestamp >= ? AND timestamp <= ?", r.indexTable) - - args := []interface{}{strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)} - - if len(queryParams.ServiceName) != 0 { - query = query + " AND serviceName = ?" - args = append(args, queryParams.ServiceName) - } - - if len(queryParams.OperationName) != 0 { - - query = query + " AND name = ?" - args = append(args, queryParams.OperationName) - - } - - if len(queryParams.Kind) != 0 { - query = query + " AND kind = ?" - args = append(args, queryParams.Kind) - - } - - if len(queryParams.MinDuration) != 0 { - query = query + " AND durationNano >= ?" - args = append(args, queryParams.MinDuration) - } - if len(queryParams.MaxDuration) != 0 { - query = query + " AND durationNano <= ?" - args = append(args, queryParams.MaxDuration) - } - - for _, item := range queryParams.Tags { - - if item.Key == "error" && item.Value == "true" { - query = query + " AND ( has(tags, 'error:true') OR statusCode>=500 OR statusCode=2)" - continue - } - - if item.Operator == "equals" { - query = query + " AND has(tags, ?)" - args = append(args, fmt.Sprintf("%s:%s", item.Key, item.Value)) - } else if item.Operator == "contains" { - query = query + " AND tagsValues[indexOf(tagsKeys, ?)] ILIKE ?" - args = append(args, item.Key) - args = append(args, fmt.Sprintf("%%%s%%", item.Value)) - } else if item.Operator == "regex" { - query = query + " AND match(tagsValues[indexOf(tagsKeys, ?)], ?)" - args = append(args, item.Key) - args = append(args, item.Value) - } else if item.Operator == "isnotnull" { - query = query + " AND has(tagsKeys, ?)" - args = append(args, item.Key) - } else { - return nil, fmt.Errorf("Tag Operator %s not supported", item.Operator) - } - - } - - query = query + " ORDER BY timestamp DESC LIMIT 100" - - var searchScanReponses []model.SearchSpanReponseItem - - err := r.db.Select(&searchScanReponses, query, args...) - - zap.S().Info(query) - - if err != nil { - zap.S().Debug("Error in processing sql query: ", err) - return nil, fmt.Errorf("Error in processing sql query") - } - - searchSpansResult := []model.SearchSpansResult{ - model.SearchSpansResult{ - Columns: []string{"__time", "SpanId", "TraceId", "ServiceName", "Name", "Kind", "DurationNano", "TagsKeys", "TagsValues"}, - Events: make([][]interface{}, len(searchScanReponses)), - }, - } - - for i, item := range searchScanReponses { - spanEvents := item.GetValues() - searchSpansResult[0].Events[i] = spanEvents - } - - return &searchSpansResult, nil } func buildFilterArrayQuery(ctx context.Context, excludeMap map[string]struct{}, params []string, filter string, query *string, args []interface{}) []interface{} { for i, e := range params { + filterKey := filter + String(5) if i == 0 && i == len(params)-1 { if _, ok := excludeMap[filter]; ok { - *query += fmt.Sprintf(" AND NOT (%s=?)", filter) + *query += fmt.Sprintf(" AND NOT (%s=@%s)", filter, filterKey) } else { - *query += fmt.Sprintf(" AND (%s=?)", filter) + *query += fmt.Sprintf(" AND (%s=@%s)", filter, filterKey) } } else if i == 0 && i != len(params)-1 { if _, ok := excludeMap[filter]; ok { - *query += fmt.Sprintf(" AND NOT (%s=?", filter) + *query += fmt.Sprintf(" AND NOT (%s=@%s", filter, filterKey) } else { - *query += fmt.Sprintf(" AND (%s=?", filter) + *query += fmt.Sprintf(" AND (%s=@%s", filter, filterKey) } } else if i != 0 && i == len(params)-1 { - *query += fmt.Sprintf(" OR %s=?)", filter) + *query += fmt.Sprintf(" OR %s=@%s)", filter, filterKey) } else { - *query += fmt.Sprintf(" OR %s=?", filter) + *query += fmt.Sprintf(" OR %s=@%s", filter, filterKey) } - args = append(args, e) + args = append(args, clickhouse.Named(filterKey, e)) } return args } @@ -1317,7 +1299,7 @@ func (r *ClickHouseReader) GetSpanFilters(ctx context.Context, queryParams *mode excludeMap[e] = struct{}{} } - args := []interface{}{strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)} + args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))} if len(queryParams.ServiceName) > 0 { args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args) } @@ -1344,186 +1326,229 @@ func (r *ClickHouseReader) GetSpanFilters(ctx context.Context, queryParams *mode } if len(queryParams.MinDuration) != 0 { - query = query + " AND durationNano >= ?" - args = append(args, queryParams.MinDuration) + query = query + " AND durationNano >= @durationNanoMin" + args = append(args, clickhouse.Named("durationNanoMin", queryParams.MinDuration)) } if len(queryParams.MaxDuration) != 0 { - query = query + " AND durationNano <= ?" - args = append(args, queryParams.MaxDuration) + query = query + " AND durationNano <= @durationNanoMax" + args = append(args, clickhouse.Named("durationNanoMax", queryParams.MaxDuration)) } query = getStatusFilters(query, queryParams.Status, excludeMap) traceFilterReponse := model.SpanFiltersResponse{ - Status: map[string]int{}, - Duration: map[string]int{}, - ServiceName: map[string]int{}, - Operation: map[string]int{}, - HttpCode: map[string]int{}, - HttpMethod: map[string]int{}, - HttpUrl: map[string]int{}, - HttpRoute: map[string]int{}, - HttpHost: map[string]int{}, - Component: map[string]int{}, + Status: map[string]uint64{}, + Duration: map[string]uint64{}, + ServiceName: map[string]uint64{}, + Operation: map[string]uint64{}, + HttpCode: map[string]uint64{}, + HttpMethod: map[string]uint64{}, + HttpUrl: map[string]uint64{}, + HttpRoute: map[string]uint64{}, + HttpHost: map[string]uint64{}, + Component: map[string]uint64{}, } for _, e := range queryParams.GetFilters { switch e { - case "serviceName": - finalQuery := fmt.Sprintf("SELECT serviceName, count() as count FROM %s WHERE timestamp >= ? AND timestamp <= ?", r.indexTable) + case constants.ServiceName: + finalQuery := fmt.Sprintf("SELECT serviceName, count() as count FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.traceDB, r.indexTable) finalQuery += query finalQuery += " GROUP BY serviceName" var dBResponse []model.DBResponseServiceName - err := r.db.Select(&dBResponse, finalQuery, args...) + err := r.db.Select(ctx, &dBResponse, finalQuery, args...) + zap.S().Info(finalQuery) + if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query", err)} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query: %s", err)} } for _, service := range dBResponse { if service.ServiceName != "" { traceFilterReponse.ServiceName[service.ServiceName] = service.Count } } - case "httpCode": - finalQuery := fmt.Sprintf("SELECT httpCode, count() as count FROM %s WHERE timestamp >= ? AND timestamp <= ?", r.indexTable) + case constants.HttpCode: + finalQuery := fmt.Sprintf("SELECT httpCode, count() as count FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.traceDB, r.indexTable) finalQuery += query finalQuery += " GROUP BY httpCode" var dBResponse []model.DBResponseHttpCode - fmt.Println(finalQuery) - err := r.db.Select(&dBResponse, finalQuery, args...) + err := r.db.Select(ctx, &dBResponse, finalQuery, args...) + zap.S().Info(finalQuery) + if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query", err)} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query: %s", err)} } for _, service := range dBResponse { if service.HttpCode != "" { traceFilterReponse.HttpCode[service.HttpCode] = service.Count } } - case "httpRoute": - finalQuery := fmt.Sprintf("SELECT httpRoute, count() as count FROM %s WHERE timestamp >= ? AND timestamp <= ?", r.indexTable) + case constants.HttpRoute: + finalQuery := fmt.Sprintf("SELECT httpRoute, count() as count FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.traceDB, r.indexTable) finalQuery += query finalQuery += " GROUP BY httpRoute" var dBResponse []model.DBResponseHttpRoute - err := r.db.Select(&dBResponse, finalQuery, args...) + err := r.db.Select(ctx, &dBResponse, finalQuery, args...) + zap.S().Info(finalQuery) + if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query", err)} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query: %s", err)} } for _, service := range dBResponse { if service.HttpRoute != "" { traceFilterReponse.HttpRoute[service.HttpRoute] = service.Count } } - case "httpUrl": - finalQuery := fmt.Sprintf("SELECT httpUrl, count() as count FROM %s WHERE timestamp >= ? AND timestamp <= ?", r.indexTable) + case constants.HttpUrl: + finalQuery := fmt.Sprintf("SELECT httpUrl, count() as count FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.traceDB, r.indexTable) finalQuery += query finalQuery += " GROUP BY httpUrl" var dBResponse []model.DBResponseHttpUrl - err := r.db.Select(&dBResponse, finalQuery, args...) + err := r.db.Select(ctx, &dBResponse, finalQuery, args...) + zap.S().Info(finalQuery) + if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query", err)} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query: %s", err)} } for _, service := range dBResponse { if service.HttpUrl != "" { traceFilterReponse.HttpUrl[service.HttpUrl] = service.Count } } - case "httpMethod": - finalQuery := fmt.Sprintf("SELECT httpMethod, count() as count FROM %s WHERE timestamp >= ? AND timestamp <= ?", r.indexTable) + case constants.HttpMethod: + finalQuery := fmt.Sprintf("SELECT httpMethod, count() as count FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.traceDB, r.indexTable) finalQuery += query finalQuery += " GROUP BY httpMethod" var dBResponse []model.DBResponseHttpMethod - err := r.db.Select(&dBResponse, finalQuery, args...) + err := r.db.Select(ctx, &dBResponse, finalQuery, args...) + zap.S().Info(finalQuery) + if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query", err)} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query: %s", err)} } for _, service := range dBResponse { if service.HttpMethod != "" { traceFilterReponse.HttpMethod[service.HttpMethod] = service.Count } } - case "httpHost": - finalQuery := fmt.Sprintf("SELECT httpHost, count() as count FROM %s WHERE timestamp >= ? AND timestamp <= ?", r.indexTable) + case constants.HttpHost: + finalQuery := fmt.Sprintf("SELECT httpHost, count() as count FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.traceDB, r.indexTable) finalQuery += query finalQuery += " GROUP BY httpHost" var dBResponse []model.DBResponseHttpHost - err := r.db.Select(&dBResponse, finalQuery, args...) + err := r.db.Select(ctx, &dBResponse, finalQuery, args...) + zap.S().Info(finalQuery) + if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query", err)} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query: %s", err)} } for _, service := range dBResponse { if service.HttpHost != "" { traceFilterReponse.HttpHost[service.HttpHost] = service.Count } } - case "operation": - finalQuery := fmt.Sprintf("SELECT name, count() as count FROM %s WHERE timestamp >= ? AND timestamp <= ?", r.indexTable) + case constants.OperationRequest: + finalQuery := fmt.Sprintf("SELECT name, count() as count FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.traceDB, r.indexTable) finalQuery += query finalQuery += " GROUP BY name" var dBResponse []model.DBResponseOperation - err := r.db.Select(&dBResponse, finalQuery, args...) + err := r.db.Select(ctx, &dBResponse, finalQuery, args...) + zap.S().Info(finalQuery) + if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query", err)} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query: %s", err)} } for _, service := range dBResponse { if service.Operation != "" { traceFilterReponse.Operation[service.Operation] = service.Count } } - case "component": - finalQuery := fmt.Sprintf("SELECT component, count() as count FROM %s WHERE timestamp >= ? AND timestamp <= ?", r.indexTable) + case constants.Component: + finalQuery := fmt.Sprintf("SELECT component, count() as count FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.traceDB, r.indexTable) finalQuery += query finalQuery += " GROUP BY component" var dBResponse []model.DBResponseComponent - err := r.db.Select(&dBResponse, finalQuery, args...) + err := r.db.Select(ctx, &dBResponse, finalQuery, args...) + zap.S().Info(finalQuery) + if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query", err)} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query: %s", err)} } for _, service := range dBResponse { - if service.Component.String != "" { - traceFilterReponse.Component[service.Component.String] = service.Count + if service.Component != "" { + traceFilterReponse.Component[service.Component] = service.Count } } - case "status": - finalQuery := fmt.Sprintf("SELECT COUNT(*) as numErrors FROM %s WHERE timestamp >= ? AND timestamp <= ? AND hasError = 1", r.indexTable) + case constants.Status: + finalQuery := fmt.Sprintf("SELECT COUNT(*) as numTotal FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU AND hasError = true", r.traceDB, r.indexTable) finalQuery += query - var dBResponse []model.DBResponseErrors - err := r.db.Select(&dBResponse, finalQuery, args...) + var dBResponse []model.DBResponseTotal + err := r.db.Select(ctx, &dBResponse, finalQuery, args...) + zap.S().Info(finalQuery) + if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query", err)} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query: %s", err)} } - finalQuery2 := fmt.Sprintf("SELECT COUNT(*) as numTotal FROM %s WHERE timestamp >= ? AND timestamp <= ?", r.indexTable) + finalQuery2 := fmt.Sprintf("SELECT COUNT(*) as numTotal FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU AND hasError = false", r.traceDB, r.indexTable) finalQuery2 += query var dBResponse2 []model.DBResponseTotal - err = r.db.Select(&dBResponse2, finalQuery2, args...) + err = r.db.Select(ctx, &dBResponse2, finalQuery2, args...) + zap.S().Info(finalQuery2) + if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query", err)} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query: %s", err)} } - traceFilterReponse.Status = map[string]int{"ok": dBResponse2[0].NumTotal - dBResponse[0].NumErrors, "error": dBResponse[0].NumErrors} - case "duration": - finalQuery := fmt.Sprintf("SELECT min(durationNano), max(durationNano) FROM %s WHERE timestamp >= ? AND timestamp <= ?", r.indexTable) + if len(dBResponse) > 0 && len(dBResponse2) > 0 { + traceFilterReponse.Status = map[string]uint64{"ok": dBResponse2[0].NumTotal, "error": dBResponse[0].NumTotal} + } else if len(dBResponse) > 0 { + traceFilterReponse.Status = map[string]uint64{"ok": 0, "error": dBResponse[0].NumTotal} + } else if len(dBResponse2) > 0 { + traceFilterReponse.Status = map[string]uint64{"ok": dBResponse2[0].NumTotal, "error": 0} + } else { + traceFilterReponse.Status = map[string]uint64{"ok": 0, "error": 0} + } + case constants.Duration: + finalQuery := fmt.Sprintf("SELECT durationNano as numTotal FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.traceDB, r.durationTable) finalQuery += query - var dBResponse []model.DBResponseMinMaxDuration - err := r.db.Select(&dBResponse, finalQuery, args...) + finalQuery += " ORDER BY durationNano LIMIT 1" + var dBResponse []model.DBResponseTotal + err := r.db.Select(ctx, &dBResponse, finalQuery, args...) + zap.S().Info(finalQuery) + if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query", err)} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query: %s", err)} } - for _, service := range dBResponse { - traceFilterReponse.Duration["minDuration"] = service.MinDuration - traceFilterReponse.Duration["maxDuration"] = service.MaxDuration + finalQuery = fmt.Sprintf("SELECT durationNano as numTotal FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", r.traceDB, r.durationTable) + finalQuery += query + finalQuery += " ORDER BY durationNano DESC LIMIT 1" + var dBResponse2 []model.DBResponseTotal + err = r.db.Select(ctx, &dBResponse2, finalQuery, args...) + zap.S().Info(finalQuery) + + if err != nil { + zap.S().Debug("Error in processing sql query: ", err) + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query: %s", err)} + } + if len(dBResponse) > 0 { + traceFilterReponse.Duration["minDuration"] = dBResponse[0].NumTotal + } + if len(dBResponse2) > 0 { + traceFilterReponse.Duration["maxDuration"] = dBResponse2[0].NumTotal } default: - return nil, &model.ApiError{model.ErrorBadData, fmt.Errorf("filter type: %s not supported", e)} + return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("filter type: %s not supported", e)} } } @@ -1536,16 +1561,16 @@ func getStatusFilters(query string, statusParams []string, excludeMap map[string if _, ok := excludeMap["status"]; ok { if len(statusParams) == 1 { if statusParams[0] == "error" { - query += " AND hasError = 0" + query += " AND hasError = false" } else if statusParams[0] == "ok" { - query += " AND hasError = 1" + query += " AND hasError = true" } } } else if len(statusParams) == 1 { if statusParams[0] == "error" { - query += " AND hasError = 1" + query += " AND hasError = true" } else if statusParams[0] == "ok" { - query += " AND hasError = 0" + query += " AND hasError = false" } } return query @@ -1553,7 +1578,7 @@ func getStatusFilters(query string, statusParams []string, excludeMap map[string func (r *ClickHouseReader) GetFilteredSpans(ctx context.Context, queryParams *model.GetFilteredSpansParams) (*model.GetFilterSpansResponse, *model.ApiError) { - baseQuery := fmt.Sprintf("SELECT timestamp, spanID, traceID, serviceName, name, durationNano, httpCode, httpMethod FROM %s WHERE timestamp >= ? AND timestamp <= ?", r.indexTable) + queryTable := fmt.Sprintf("%s.%s", r.traceDB, r.indexTable) excludeMap := make(map[string]struct{}) for _, e := range queryParams.Exclude { @@ -1565,7 +1590,7 @@ func (r *ClickHouseReader) GetFilteredSpans(ctx context.Context, queryParams *mo } var query string - args := []interface{}{strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)} + args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))} if len(queryParams.ServiceName) > 0 { args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args) } @@ -1591,119 +1616,150 @@ func (r *ClickHouseReader) GetFilteredSpans(ctx context.Context, queryParams *mo args = buildFilterArrayQuery(ctx, excludeMap, queryParams.Operation, constants.OperationDB, &query, args) } if len(queryParams.MinDuration) != 0 { - query = query + " AND durationNano >= ?" - args = append(args, queryParams.MinDuration) + query = query + " AND durationNano >= @durationNanoMin" + args = append(args, clickhouse.Named("durationNanoMin", queryParams.MinDuration)) } if len(queryParams.MaxDuration) != 0 { - query = query + " AND durationNano <= ?" - args = append(args, queryParams.MaxDuration) + query = query + " AND durationNano <= @durationNanoMax" + args = append(args, clickhouse.Named("durationNanoMax", queryParams.MaxDuration)) } query = getStatusFilters(query, queryParams.Status, excludeMap) if len(queryParams.Kind) != 0 { - query = query + " AND kind = ?" - args = append(args, queryParams.Kind) + query = query + " AND kind = @kind" + args = append(args, clickhouse.Named("kind", queryParams.Kind)) } - for _, item := range queryParams.Tags { - - if item.Operator == "in" { - for i, value := range item.Values { - if i == 0 && i == len(item.Values)-1 { - query += " AND has(tags, ?)" - } else if i == 0 && i != len(item.Values)-1 { - query += " AND (has(tags, ?)" - } else if i != 0 && i == len(item.Values)-1 { - query += " OR has(tags, ?))" - } else { - query += " OR has(tags, ?)" - } - args = append(args, fmt.Sprintf("%s:%s", item.Key, value)) - } - } else if item.Operator == "not in" { - for i, value := range item.Values { - if i == 0 && i == len(item.Values)-1 { - query += " AND NOT has(tags, ?)" - } else if i == 0 && i != len(item.Values)-1 { - query += " AND NOT (has(tags, ?)" - } else if i != 0 && i == len(item.Values)-1 { - query += " OR has(tags, ?))" - } else { - query += " OR has(tags, ?)" - } - args = append(args, fmt.Sprintf("%s:%s", item.Key, value)) - } - } else if item.Operator == "regex" { - if len(item.Values) != 1 { - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Regex tag operator should only have one value")} - } - query = query + " AND match(tagsValues[indexOf(tagsKeys, ?)], ?)" - args = append(args, item.Key) - args = append(args, item.Values[0]) - } else if item.Operator == "isnotnull" { - for range item.Values { - query = query + " AND has(tagsKeys, ?)" - args = append(args, item.Key) - } - } else { - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Tag Operator %s not supported", item.Operator)} - } - + args, errStatus := buildQueryWithTagParams(ctx, queryParams.Tags, &query, args) + if errStatus != nil { + return nil, errStatus } - var totalSpans []model.DBResponseTotal + if len(queryParams.OrderParam) != 0 { + if queryParams.OrderParam == constants.Duration { + queryTable = fmt.Sprintf("%s.%s", r.traceDB, r.durationTable) + if queryParams.Order == constants.Descending { + query = query + " ORDER BY durationNano DESC" + } + if queryParams.Order == constants.Ascending { + query = query + " ORDER BY durationNano ASC" + } + } else if queryParams.OrderParam == constants.Timestamp { + projectionOptQuery := "SET allow_experimental_projection_optimization = 1" + err := r.db.Exec(ctx, projectionOptQuery) - totalSpansQuery := fmt.Sprintf(`SELECT count() as numTotal FROM %s WHERE timestamp >= ? AND timestamp <= ?`, r.indexTable) + zap.S().Info(projectionOptQuery) - totalSpansQuery += query - err := r.db.Select(&totalSpans, totalSpansQuery, args...) - - zap.S().Info(totalSpansQuery) - - if err != nil { - zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query")} - } - - if len(queryParams.Order) != 0 { - if queryParams.Order == "descending" { - query = query + " ORDER BY timestamp DESC" - } - if queryParams.Order == "ascending" { - query = query + " ORDER BY timestamp ASC" + if err != nil { + zap.S().Debug("Error in processing sql query: ", err) + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query")} + } + if queryParams.Order == constants.Descending { + query = query + " ORDER BY timestamp DESC" + } + if queryParams.Order == constants.Ascending { + query = query + " ORDER BY timestamp ASC" + } } } if queryParams.Limit > 0 { - query = query + " LIMIT ?" - args = append(args, queryParams.Limit) + query = query + " LIMIT @limit" + args = append(args, clickhouse.Named("limit", queryParams.Limit)) } if queryParams.Offset > 0 { - // due to bug in SQLx driver, using %d temporarily - query = query + fmt.Sprintf(" OFFSET %d", queryParams.Offset) - // args = append(args, queryParams.Offset) + query = query + " OFFSET @offset" + args = append(args, clickhouse.Named("offset", queryParams.Offset)) } var getFilterSpansResponseItems []model.GetFilterSpansResponseItem + baseQuery := fmt.Sprintf("SELECT timestamp, spanID, traceID, serviceName, name, durationNano, httpCode, gRPCCode, gRPCMethod, httpMethod FROM %s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryTable) baseQuery += query - err = r.db.Select(&getFilterSpansResponseItems, baseQuery, args...) + err := r.db.Select(ctx, &getFilterSpansResponseItems, baseQuery, args...) + // Fill status and method + for i, e := range getFilterSpansResponseItems { + if e.HttpCode == "" { + getFilterSpansResponseItems[i].StatusCode = e.GRPCode + } else { + getFilterSpansResponseItems[i].StatusCode = e.HttpCode + } + if e.HttpMethod == "" { + getFilterSpansResponseItems[i].Method = e.GRPMethod + } else { + getFilterSpansResponseItems[i].Method = e.HttpMethod + } + } zap.S().Info(baseQuery) if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query")} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query")} } getFilterSpansResponse := model.GetFilterSpansResponse{ Spans: getFilterSpansResponseItems, - TotalSpans: totalSpans[0].NumTotal, + TotalSpans: 1000, } return &getFilterSpansResponse, nil } +func StringWithCharset(length int, charset string) string { + b := make([]byte, length) + for i := range b { + b[i] = charset[seededRand.Intn(len(charset))] + } + return string(b) +} + +func String(length int) string { + return StringWithCharset(length, charset) +} + +func buildQueryWithTagParams(ctx context.Context, tags []model.TagQuery, query *string, args []interface{}) ([]interface{}, *model.ApiError) { + + for _, item := range tags { + if item.Operator == "in" { + for i, value := range item.Values { + tagKey := "inTagKey" + String(5) + tagValue := "inTagValue" + String(5) + if i == 0 && i == len(item.Values)-1 { + *query += fmt.Sprintf(" AND tagMap[@%s] = @%s", tagKey, tagValue) + } else if i == 0 && i != len(item.Values)-1 { + *query += fmt.Sprintf(" AND (tagMap[@%s] = @%s", tagKey, tagValue) + } else if i != 0 && i == len(item.Values)-1 { + *query += fmt.Sprintf(" OR tagMap[@%s] = @%s)", tagKey, tagValue) + } else { + *query += fmt.Sprintf(" OR tagMap[@%s] = @%s", tagKey, tagValue) + } + args = append(args, clickhouse.Named(tagKey, item.Key)) + args = append(args, clickhouse.Named(tagValue, value)) + } + } else if item.Operator == "not in" { + for i, value := range item.Values { + tagKey := "notinTagKey" + String(5) + tagValue := "notinTagValue" + String(5) + if i == 0 && i == len(item.Values)-1 { + *query += fmt.Sprintf(" AND NOT tagMap[@%s] = @%s", tagKey, tagValue) + } else if i == 0 && i != len(item.Values)-1 { + *query += fmt.Sprintf(" AND NOT (tagMap[@%s] = @%s", tagKey, tagValue) + } else if i != 0 && i == len(item.Values)-1 { + *query += fmt.Sprintf(" OR tagMap[@%s] = @%s)", tagKey, tagValue) + } else { + *query += fmt.Sprintf(" OR tagMap[@%s] = @%s", tagKey, tagValue) + } + args = append(args, clickhouse.Named(tagKey, item.Key)) + args = append(args, clickhouse.Named(tagValue, value)) + } + } else { + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Tag Operator %s not supported", item.Operator)} + } + } + return args, nil +} + func (r *ClickHouseReader) GetTagFilters(ctx context.Context, queryParams *model.TagFilterParams) (*[]model.TagFilters, *model.ApiError) { excludeMap := make(map[string]struct{}) @@ -1716,7 +1772,7 @@ func (r *ClickHouseReader) GetTagFilters(ctx context.Context, queryParams *model } var query string - args := []interface{}{strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)} + args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))} if len(queryParams.ServiceName) > 0 { args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args) } @@ -1742,28 +1798,28 @@ func (r *ClickHouseReader) GetTagFilters(ctx context.Context, queryParams *model args = buildFilterArrayQuery(ctx, excludeMap, queryParams.Operation, constants.OperationDB, &query, args) } if len(queryParams.MinDuration) != 0 { - query = query + " AND durationNano >= ?" - args = append(args, queryParams.MinDuration) + query = query + " AND durationNano >= @durationNanoMin" + args = append(args, clickhouse.Named("durationNanoMin", queryParams.MinDuration)) } if len(queryParams.MaxDuration) != 0 { - query = query + " AND durationNano <= ?" - args = append(args, queryParams.MaxDuration) + query = query + " AND durationNano <= @durationNanoMax" + args = append(args, clickhouse.Named("durationNanoMax", queryParams.MaxDuration)) } query = getStatusFilters(query, queryParams.Status, excludeMap) tagFilters := []model.TagFilters{} - finalQuery := fmt.Sprintf(`SELECT DISTINCT arrayJoin(tagsKeys) as tagKeys FROM %s WHERE timestamp >= ? AND timestamp <= ?`, r.indexTable) + finalQuery := fmt.Sprintf(`SELECT DISTINCT arrayJoin(tagMap.keys) as tagKeys FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU`, r.traceDB, r.indexTable) + // Alternative query: SELECT groupUniqArrayArray(mapKeys(tagMap)) as tagKeys FROM signoz_index_v2 finalQuery += query - fmt.Println(finalQuery) - err := r.db.Select(&tagFilters, finalQuery, args...) + err := r.db.Select(ctx, &tagFilters, finalQuery, args...) zap.S().Info(query) if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query")} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query")} } tagFilters = excludeTags(ctx, tagFilters) @@ -1782,6 +1838,7 @@ func excludeTags(ctx context.Context, tags []model.TagFilters) []model.TagFilter "messaging.operation": true, "component": true, "error": true, + "service.name": true, } var newTags []model.TagFilters for _, tag := range tags { @@ -1805,7 +1862,7 @@ func (r *ClickHouseReader) GetTagValues(ctx context.Context, queryParams *model. } var query string - args := []interface{}{queryParams.TagKey, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)} + args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))} if len(queryParams.ServiceName) > 0 { args = buildFilterArrayQuery(ctx, excludeMap, queryParams.ServiceName, constants.ServiceName, &query, args) } @@ -1831,30 +1888,29 @@ func (r *ClickHouseReader) GetTagValues(ctx context.Context, queryParams *model. args = buildFilterArrayQuery(ctx, excludeMap, queryParams.Operation, constants.OperationDB, &query, args) } if len(queryParams.MinDuration) != 0 { - query = query + " AND durationNano >= ?" - args = append(args, queryParams.MinDuration) + query = query + " AND durationNano >= @durationNanoMin" + args = append(args, clickhouse.Named("durationNanoMin", queryParams.MinDuration)) } if len(queryParams.MaxDuration) != 0 { - query = query + " AND durationNano <= ?" - args = append(args, queryParams.MaxDuration) + query = query + " AND durationNano <= @durationNanoMax" + args = append(args, clickhouse.Named("durationNanoMax", queryParams.MaxDuration)) } query = getStatusFilters(query, queryParams.Status, excludeMap) tagValues := []model.TagValues{} - finalQuery := fmt.Sprintf(`SELECT tagMap[?] as tagValues FROM %s WHERE timestamp >= ? AND timestamp <= ?`, r.indexTable) + finalQuery := fmt.Sprintf(`SELECT tagMap[@key] as tagValues FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU`, r.traceDB, r.indexTable) finalQuery += query - fmt.Println(finalQuery) - finalQuery += "GROUP BY tagMap[?]" - args = append(args, queryParams.TagKey) - err := r.db.Select(&tagValues, finalQuery, args...) + finalQuery += " GROUP BY tagMap[@key]" + args = append(args, clickhouse.Named("key", queryParams.TagKey)) + err := r.db.Select(ctx, &tagValues, finalQuery, args...) zap.S().Info(query) if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query")} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query")} } cleanedTagValues := []model.TagValues{} @@ -1866,161 +1922,24 @@ func (r *ClickHouseReader) GetTagValues(ctx context.Context, queryParams *model. return &cleanedTagValues, nil } -func (r *ClickHouseReader) GetServiceDBOverview(ctx context.Context, queryParams *model.GetServiceOverviewParams) (*[]model.ServiceDBOverviewItem, error) { - - var serviceDBOverviewItems []model.ServiceDBOverviewItem - - query := fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %s minute) as time, avg(durationNano) as avgDuration, count(1) as numCalls, dbSystem FROM %s WHERE serviceName='%s' AND timestamp>='%s' AND timestamp<='%s' AND kind='3' AND dbName IS NOT NULL GROUP BY time, dbSystem ORDER BY time DESC", strconv.Itoa(int(queryParams.StepSeconds/60)), r.indexTable, queryParams.ServiceName, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)) - - err := r.db.Select(&serviceDBOverviewItems, query) - - zap.S().Info(query) - - if err != nil { - zap.S().Debug("Error in processing sql query: ", err) - return nil, fmt.Errorf("Error in processing sql query") - } - - for i, _ := range serviceDBOverviewItems { - timeObj, _ := time.Parse(time.RFC3339Nano, serviceDBOverviewItems[i].Time) - serviceDBOverviewItems[i].Timestamp = int64(timeObj.UnixNano()) - serviceDBOverviewItems[i].Time = "" - serviceDBOverviewItems[i].CallRate = float32(serviceDBOverviewItems[i].NumCalls) / float32(queryParams.StepSeconds) - } - - if serviceDBOverviewItems == nil { - serviceDBOverviewItems = []model.ServiceDBOverviewItem{} - } - - return &serviceDBOverviewItems, nil - -} - -func (r *ClickHouseReader) GetServiceExternalAvgDuration(ctx context.Context, queryParams *model.GetServiceOverviewParams) (*[]model.ServiceExternalItem, error) { - - var serviceExternalItems []model.ServiceExternalItem - - query := fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %s minute) as time, avg(durationNano) as avgDuration FROM %s WHERE serviceName='%s' AND timestamp>='%s' AND timestamp<='%s' AND kind='3' AND externalHttpUrl IS NOT NULL GROUP BY time ORDER BY time DESC", strconv.Itoa(int(queryParams.StepSeconds/60)), r.indexTable, queryParams.ServiceName, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)) - - err := r.db.Select(&serviceExternalItems, query) - - zap.S().Info(query) - - if err != nil { - zap.S().Debug("Error in processing sql query: ", err) - return nil, fmt.Errorf("Error in processing sql query") - } - - for i, _ := range serviceExternalItems { - timeObj, _ := time.Parse(time.RFC3339Nano, serviceExternalItems[i].Time) - serviceExternalItems[i].Timestamp = int64(timeObj.UnixNano()) - serviceExternalItems[i].Time = "" - serviceExternalItems[i].CallRate = float32(serviceExternalItems[i].NumCalls) / float32(queryParams.StepSeconds) - } - - if serviceExternalItems == nil { - serviceExternalItems = []model.ServiceExternalItem{} - } - - return &serviceExternalItems, nil -} - -func (r *ClickHouseReader) GetServiceExternalErrors(ctx context.Context, queryParams *model.GetServiceOverviewParams) (*[]model.ServiceExternalItem, error) { - - var serviceExternalErrorItems []model.ServiceExternalItem - - query := fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %s minute) as time, avg(durationNano) as avgDuration, count(1) as numCalls, externalHttpUrl FROM %s WHERE serviceName='%s' AND timestamp>='%s' AND timestamp<='%s' AND kind='3' AND externalHttpUrl IS NOT NULL AND (statusCode >= 500 OR statusCode=2) GROUP BY time, externalHttpUrl ORDER BY time DESC", strconv.Itoa(int(queryParams.StepSeconds/60)), r.indexTable, queryParams.ServiceName, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)) - - err := r.db.Select(&serviceExternalErrorItems, query) - - zap.S().Info(query) - - if err != nil { - zap.S().Debug("Error in processing sql query: ", err) - return nil, fmt.Errorf("Error in processing sql query") - } - var serviceExternalTotalItems []model.ServiceExternalItem - - queryTotal := fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %s minute) as time, avg(durationNano) as avgDuration, count(1) as numCalls, externalHttpUrl FROM %s WHERE serviceName='%s' AND timestamp>='%s' AND timestamp<='%s' AND kind='3' AND externalHttpUrl IS NOT NULL GROUP BY time, externalHttpUrl ORDER BY time DESC", strconv.Itoa(int(queryParams.StepSeconds/60)), r.indexTable, queryParams.ServiceName, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)) - - errTotal := r.db.Select(&serviceExternalTotalItems, queryTotal) - - if errTotal != nil { - zap.S().Debug("Error in processing sql query: ", err) - return nil, fmt.Errorf("Error in processing sql query") - } - - m := make(map[string]int) - - for j, _ := range serviceExternalErrorItems { - timeObj, _ := time.Parse(time.RFC3339Nano, serviceExternalErrorItems[j].Time) - m[strconv.FormatInt(timeObj.UnixNano(), 10)+"-"+serviceExternalErrorItems[j].ExternalHttpUrl] = serviceExternalErrorItems[j].NumCalls - } - - for i, _ := range serviceExternalTotalItems { - timeObj, _ := time.Parse(time.RFC3339Nano, serviceExternalTotalItems[i].Time) - serviceExternalTotalItems[i].Timestamp = int64(timeObj.UnixNano()) - serviceExternalTotalItems[i].Time = "" - // serviceExternalTotalItems[i].CallRate = float32(serviceExternalTotalItems[i].NumCalls) / float32(queryParams.StepSeconds) - - if val, ok := m[strconv.FormatInt(serviceExternalTotalItems[i].Timestamp, 10)+"-"+serviceExternalTotalItems[i].ExternalHttpUrl]; ok { - serviceExternalTotalItems[i].NumErrors = val - serviceExternalTotalItems[i].ErrorRate = float32(serviceExternalTotalItems[i].NumErrors) * 100 / float32(serviceExternalTotalItems[i].NumCalls) - } - serviceExternalTotalItems[i].CallRate = 0 - serviceExternalTotalItems[i].NumCalls = 0 - - } - - if serviceExternalTotalItems == nil { - serviceExternalTotalItems = []model.ServiceExternalItem{} - } - - return &serviceExternalTotalItems, nil -} - -func (r *ClickHouseReader) GetServiceExternal(ctx context.Context, queryParams *model.GetServiceOverviewParams) (*[]model.ServiceExternalItem, error) { - - var serviceExternalItems []model.ServiceExternalItem - - query := fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %s minute) as time, avg(durationNano) as avgDuration, count(1) as numCalls, externalHttpUrl FROM %s WHERE serviceName='%s' AND timestamp>='%s' AND timestamp<='%s' AND kind='3' AND externalHttpUrl IS NOT NULL GROUP BY time, externalHttpUrl ORDER BY time DESC", strconv.Itoa(int(queryParams.StepSeconds/60)), r.indexTable, queryParams.ServiceName, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)) - - err := r.db.Select(&serviceExternalItems, query) - - zap.S().Info(query) - - if err != nil { - zap.S().Debug("Error in processing sql query: ", err) - return nil, fmt.Errorf("Error in processing sql query") - } - - for i, _ := range serviceExternalItems { - timeObj, _ := time.Parse(time.RFC3339Nano, serviceExternalItems[i].Time) - serviceExternalItems[i].Timestamp = int64(timeObj.UnixNano()) - serviceExternalItems[i].Time = "" - serviceExternalItems[i].CallRate = float32(serviceExternalItems[i].NumCalls) / float32(queryParams.StepSeconds) - } - - if serviceExternalItems == nil { - serviceExternalItems = []model.ServiceExternalItem{} - } - - return &serviceExternalItems, nil -} - -func (r *ClickHouseReader) GetTopEndpoints(ctx context.Context, queryParams *model.GetTopEndpointsParams) (*[]model.TopEndpointsItem, error) { +func (r *ClickHouseReader) GetTopEndpoints(ctx context.Context, queryParams *model.GetTopEndpointsParams) (*[]model.TopEndpointsItem, *model.ApiError) { var topEndpointsItems []model.TopEndpointsItem - query := fmt.Sprintf("SELECT quantile(0.5)(durationNano) as p50, quantile(0.95)(durationNano) as p95, quantile(0.99)(durationNano) as p99, COUNT(1) as numCalls, name FROM %s WHERE timestamp >= '%s' AND timestamp <= '%s' AND kind='2' and serviceName='%s' GROUP BY name", r.indexTable, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10), queryParams.ServiceName) - - err := r.db.Select(&topEndpointsItems, query) + query := fmt.Sprintf("SELECT quantile(0.5)(durationNano) as p50, quantile(0.95)(durationNano) as p95, quantile(0.99)(durationNano) as p99, COUNT(1) as numCalls, name FROM %s.%s WHERE timestamp >= '%s' AND timestamp <= '%s' AND kind='2' and serviceName='%s'", r.traceDB, r.indexTable, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10), queryParams.ServiceName) + args := []interface{}{} + args, errStatus := buildQueryWithTagParams(ctx, queryParams.Tags, &query, args) + if errStatus != nil { + return nil, errStatus + } + query += " GROUP BY name" + err := r.db.Select(ctx, &topEndpointsItems, query, args...) zap.S().Info(query) if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, fmt.Errorf("Error in processing sql query") + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query")} } if topEndpointsItems == nil { @@ -2036,12 +1955,12 @@ func (r *ClickHouseReader) GetUsage(ctx context.Context, queryParams *model.GetU var query string if len(queryParams.ServiceName) != 0 { - query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d HOUR) as time, count(1) as count FROM %s WHERE serviceName='%s' AND timestamp>='%s' AND timestamp<='%s' GROUP BY time ORDER BY time ASC", queryParams.StepHour, r.indexTable, queryParams.ServiceName, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)) + query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d HOUR) as time, count(1) as count FROM %s.%s WHERE serviceName='%s' AND timestamp>='%s' AND timestamp<='%s' GROUP BY time ORDER BY time ASC", queryParams.StepHour, r.traceDB, r.indexTable, queryParams.ServiceName, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)) } else { - query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d HOUR) as time, count(1) as count FROM %s WHERE timestamp>='%s' AND timestamp<='%s' GROUP BY time ORDER BY time ASC", queryParams.StepHour, r.indexTable, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)) + query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d HOUR) as time, count(1) as count FROM %s.%s WHERE timestamp>='%s' AND timestamp<='%s' GROUP BY time ORDER BY time ASC", queryParams.StepHour, r.traceDB, r.indexTable, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)) } - err := r.db.Select(&usageItems, query) + err := r.db.Select(ctx, &usageItems, query) zap.S().Info(query) @@ -2050,10 +1969,8 @@ func (r *ClickHouseReader) GetUsage(ctx context.Context, queryParams *model.GetU return nil, fmt.Errorf("Error in processing sql query") } - for i, _ := range usageItems { - timeObj, _ := time.Parse(time.RFC3339Nano, usageItems[i].Time) - usageItems[i].Timestamp = int64(timeObj.UnixNano()) - usageItems[i].Time = "" + for i := range usageItems { + usageItems[i].Timestamp = uint64(usageItems[i].Time.UnixNano()) } if usageItems == nil { @@ -2063,66 +1980,13 @@ func (r *ClickHouseReader) GetUsage(ctx context.Context, queryParams *model.GetU return &usageItems, nil } -func (r *ClickHouseReader) GetServicesList(ctx context.Context) (*[]string, error) { - - services := []string{} - - query := fmt.Sprintf(`SELECT DISTINCT serviceName FROM %s WHERE toDate(timestamp) > now() - INTERVAL 1 DAY`, r.indexTable) - - err := r.db.Select(&services, query) - - zap.S().Info(query) - - if err != nil { - zap.S().Debug("Error in processing sql query: ", err) - return nil, fmt.Errorf("Error in processing sql query") - } - - return &services, nil -} - -func (r *ClickHouseReader) GetTags(ctx context.Context, serviceName string) (*[]model.TagItem, error) { - - tagItems := []model.TagItem{} - - query := fmt.Sprintf(`SELECT DISTINCT arrayJoin(tagsKeys) as tagKeys FROM %s WHERE serviceName=? AND toDate(timestamp) > now() - INTERVAL 1 DAY`, r.indexTable) - - err := r.db.Select(&tagItems, query, serviceName) - - zap.S().Info(query) - - if err != nil { - zap.S().Debug("Error in processing sql query: ", err) - return nil, fmt.Errorf("Error in processing sql query") - } - - return &tagItems, nil -} - -func (r *ClickHouseReader) GetOperations(ctx context.Context, serviceName string) (*[]string, error) { - - operations := []string{} - - query := fmt.Sprintf(`SELECT DISTINCT(name) FROM %s WHERE serviceName=? AND toDate(timestamp) > now() - INTERVAL 1 DAY`, r.indexTable) - - err := r.db.Select(&operations, query, serviceName) - - zap.S().Info(query) - - if err != nil { - zap.S().Debug("Error in processing sql query: ", err) - return nil, fmt.Errorf("Error in processing sql query") - } - return &operations, nil -} - func (r *ClickHouseReader) SearchTraces(ctx context.Context, traceId string) (*[]model.SearchSpansResult, error) { - var searchScanReponses []model.SearchSpanReponseItem + var searchScanReponses []model.SearchSpanDBReponseItem - query := fmt.Sprintf("SELECT timestamp, spanID, traceID, serviceName, name, kind, durationNano, tagsKeys, tagsValues, references, events, hasError FROM %s WHERE traceID=?", r.indexTable) + query := fmt.Sprintf("SELECT timestamp, traceID, model FROM %s.%s WHERE traceID=$1", r.traceDB, r.spansTable) - err := r.db.Select(&searchScanReponses, query, traceId) + err := r.db.Select(ctx, &searchScanReponses, query, traceId) zap.S().Info(query) @@ -2131,27 +1995,37 @@ func (r *ClickHouseReader) SearchTraces(ctx context.Context, traceId string) (*[ return nil, fmt.Errorf("Error in processing sql query") } - searchSpansResult := []model.SearchSpansResult{ - model.SearchSpansResult{ - Columns: []string{"__time", "SpanId", "TraceId", "ServiceName", "Name", "Kind", "DurationNano", "TagsKeys", "TagsValues", "References", "Events", "HasError"}, - Events: make([][]interface{}, len(searchScanReponses)), - }, + searchSpansResult := []model.SearchSpansResult{{ + Columns: []string{"__time", "SpanId", "TraceId", "ServiceName", "Name", "Kind", "DurationNano", "TagsKeys", "TagsValues", "References", "Events", "HasError"}, + Events: make([][]interface{}, len(searchScanReponses)), + }, } for i, item := range searchScanReponses { - spanEvents := item.GetValues() + var jsonItem model.SearchSpanReponseItem + json.Unmarshal([]byte(item.Model), &jsonItem) + jsonItem.TimeUnixNano = uint64(item.Timestamp.UnixNano() / 1000000) + spanEvents := jsonItem.GetValues() searchSpansResult[0].Events[i] = spanEvents } return &searchSpansResult, nil } +func interfaceArrayToStringArray(array []interface{}) []string { + var strArray []string + for _, item := range array { + strArray = append(strArray, item.(string)) + } + return strArray +} + func (r *ClickHouseReader) GetServiceMapDependencies(ctx context.Context, queryParams *model.GetServicesParams) (*[]model.ServiceMapDependencyResponseItem, error) { serviceMapDependencyItems := []model.ServiceMapDependencyItem{} - query := fmt.Sprintf(`SELECT spanID, parentSpanID, serviceName FROM %s WHERE timestamp>='%s' AND timestamp<='%s'`, r.indexTable, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)) + query := fmt.Sprintf(`SELECT spanID, parentSpanID, serviceName FROM %s.%s WHERE timestamp>='%s' AND timestamp<='%s'`, r.traceDB, r.indexTable, strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)) - err := r.db.Select(&serviceMapDependencyItems, query) + err := r.db.Select(ctx, &serviceMapDependencyItems, query) zap.S().Info(query) @@ -2163,10 +2037,10 @@ func (r *ClickHouseReader) GetServiceMapDependencies(ctx context.Context, queryP serviceMap := make(map[string]*model.ServiceMapDependencyResponseItem) spanId2ServiceNameMap := make(map[string]string) - for i, _ := range serviceMapDependencyItems { + for i := range serviceMapDependencyItems { spanId2ServiceNameMap[serviceMapDependencyItems[i].SpanId] = serviceMapDependencyItems[i].ServiceName } - for i, _ := range serviceMapDependencyItems { + for i := range serviceMapDependencyItems { parent2childServiceName := spanId2ServiceNameMap[serviceMapDependencyItems[i].ParentSpanId] + "-" + spanId2ServiceNameMap[serviceMapDependencyItems[i].SpanId] if _, ok := serviceMap[parent2childServiceName]; !ok { serviceMap[parent2childServiceName] = &model.ServiceMapDependencyResponseItem{ @@ -2190,112 +2064,6 @@ func (r *ClickHouseReader) GetServiceMapDependencies(ctx context.Context, queryP return &retMe, nil } -func (r *ClickHouseReader) SearchSpansAggregate(ctx context.Context, queryParams *model.SpanSearchAggregatesParams) ([]model.SpanSearchAggregatesResponseItem, error) { - - spanSearchAggregatesResponseItems := []model.SpanSearchAggregatesResponseItem{} - - aggregation_query := "" - if queryParams.Dimension == "duration" { - switch queryParams.AggregationOption { - case "p50": - aggregation_query = " quantile(0.50)(durationNano) as value " - break - - case "p95": - aggregation_query = " quantile(0.95)(durationNano) as value " - break - - case "p99": - aggregation_query = " quantile(0.99)(durationNano) as value " - break - } - } else if queryParams.Dimension == "calls" { - aggregation_query = " count(*) as value " - } - - query := fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, %s FROM %s WHERE timestamp >= ? AND timestamp <= ?", queryParams.StepSeconds/60, aggregation_query, r.indexTable) - - args := []interface{}{strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)} - - if len(queryParams.ServiceName) != 0 { - query = query + " AND serviceName = ?" - args = append(args, queryParams.ServiceName) - } - - if len(queryParams.OperationName) != 0 { - - query = query + " AND name = ?" - args = append(args, queryParams.OperationName) - - } - - if len(queryParams.Kind) != 0 { - query = query + " AND kind = ?" - args = append(args, queryParams.Kind) - - } - - if len(queryParams.MinDuration) != 0 { - query = query + " AND durationNano >= ?" - args = append(args, queryParams.MinDuration) - } - if len(queryParams.MaxDuration) != 0 { - query = query + " AND durationNano <= ?" - args = append(args, queryParams.MaxDuration) - } - - for _, item := range queryParams.Tags { - - if item.Key == "error" && item.Value == "true" { - query = query + " AND ( has(tags, 'error:true') OR statusCode>=500 OR statusCode=2)" - continue - } - - if item.Operator == "equals" { - query = query + " AND has(tags, ?)" - args = append(args, fmt.Sprintf("%s:%s", item.Key, item.Value)) - } else if item.Operator == "contains" { - query = query + " AND tagsValues[indexOf(tagsKeys, ?)] ILIKE ?" - args = append(args, item.Key) - args = append(args, fmt.Sprintf("%%%s%%", item.Value)) - } else if item.Operator == "regex" { - query = query + " AND match(tagsValues[indexOf(tagsKeys, ?)], ?)" - args = append(args, item.Key) - args = append(args, item.Value) - } else if item.Operator == "isnotnull" { - query = query + " AND has(tagsKeys, ?)" - args = append(args, item.Key) - } else { - return nil, fmt.Errorf("Tag Operator %s not supported", item.Operator) - } - - } - - query = query + " GROUP BY time ORDER BY time" - - err := r.db.Select(&spanSearchAggregatesResponseItems, query, args...) - - zap.S().Info(query) - - if err != nil { - zap.S().Debug("Error in processing sql query: ", err) - return nil, fmt.Errorf("Error in processing sql query") - } - - for i, _ := range spanSearchAggregatesResponseItems { - - timeObj, _ := time.Parse(time.RFC3339Nano, spanSearchAggregatesResponseItems[i].Time) - spanSearchAggregatesResponseItems[i].Timestamp = int64(timeObj.UnixNano()) - spanSearchAggregatesResponseItems[i].Time = "" - if queryParams.AggregationOption == "rate_per_sec" { - spanSearchAggregatesResponseItems[i].Value = float32(spanSearchAggregatesResponseItems[i].Value) / float32(queryParams.StepSeconds) - } - } - - return spanSearchAggregatesResponseItems, nil - -} - func (r *ClickHouseReader) GetFilteredSpansAggregates(ctx context.Context, queryParams *model.GetFilteredSpanAggregatesParams) (*model.GetFilteredSpansAggregatesResponse, *model.ApiError) { excludeMap := make(map[string]struct{}) @@ -2313,64 +2081,64 @@ func (r *ClickHouseReader) GetFilteredSpansAggregates(ctx context.Context, query if queryParams.Dimension == "duration" { switch queryParams.AggregationOption { case "p50": - aggregation_query = " quantile(0.50)(durationNano) as value " + aggregation_query = " quantile(0.50)(durationNano) as float64Value " case "p95": - aggregation_query = " quantile(0.95)(durationNano) as value " + aggregation_query = " quantile(0.95)(durationNano) as float64Value " case "p90": - aggregation_query = " quantile(0.90)(durationNano) as value " + aggregation_query = " quantile(0.90)(durationNano) as float64Value " case "p99": - aggregation_query = " quantile(0.99)(durationNano) as value " + aggregation_query = " quantile(0.99)(durationNano) as float64Value " case "max": aggregation_query = " max(durationNano) as value " case "min": aggregation_query = " min(durationNano) as value " case "avg": - aggregation_query = " avg(durationNano) as value " + aggregation_query = " avg(durationNano) as float64Value " case "sum": aggregation_query = " sum(durationNano) as value " default: - return nil, &model.ApiError{model.ErrorBadData, fmt.Errorf("Aggregate type: %s not supported", queryParams.AggregationOption)} + return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("Aggregate type: %s not supported", queryParams.AggregationOption)} } } else if queryParams.Dimension == "calls" { aggregation_query = " count(*) as value " } - args := []interface{}{strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)} + args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))} var query string if queryParams.GroupBy != "" { switch queryParams.GroupBy { - case "serviceName": - query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, serviceName as groupBy, %s FROM %s WHERE timestamp >= ? AND timestamp <= ?", queryParams.StepSeconds/60, aggregation_query, r.indexTable) - case "httpCode": - query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, httpCode as groupBy, %s FROM %s WHERE timestamp >= ? AND timestamp <= ?", queryParams.StepSeconds/60, aggregation_query, r.indexTable) - case "httpMethod": - query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, httpMethod as groupBy, %s FROM %s WHERE timestamp >= ? AND timestamp <= ?", queryParams.StepSeconds/60, aggregation_query, r.indexTable) - case "httpUrl": - query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, httpUrl as groupBy, %s FROM %s WHERE timestamp >= ? AND timestamp <= ?", queryParams.StepSeconds/60, aggregation_query, r.indexTable) - case "httpRoute": - query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, httpRoute as groupBy, %s FROM %s WHERE timestamp >= ? AND timestamp <= ?", queryParams.StepSeconds/60, aggregation_query, r.indexTable) - case "httpHost": - query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, httpHost as groupBy, %s FROM %s WHERE timestamp >= ? AND timestamp <= ?", queryParams.StepSeconds/60, aggregation_query, r.indexTable) - case "dbName": - query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, dbName as groupBy, %s FROM %s WHERE timestamp >= ? AND timestamp <= ?", queryParams.StepSeconds/60, aggregation_query, r.indexTable) - case "dbOperation": - query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, dbOperation as groupBy, %s FROM %s WHERE timestamp >= ? AND timestamp <= ?", queryParams.StepSeconds/60, aggregation_query, r.indexTable) - case "operation": - query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, name as groupBy, %s FROM %s WHERE timestamp >= ? AND timestamp <= ?", queryParams.StepSeconds/60, aggregation_query, r.indexTable) - case "msgSystem": - query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, msgSystem as groupBy, %s FROM %s WHERE timestamp >= ? AND timestamp <= ?", queryParams.StepSeconds/60, aggregation_query, r.indexTable) - case "msgOperation": - query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, msgOperation as groupBy, %s FROM %s WHERE timestamp >= ? AND timestamp <= ?", queryParams.StepSeconds/60, aggregation_query, r.indexTable) - case "dbSystem": - query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, dbSystem as groupBy, %s FROM %s WHERE timestamp >= ? AND timestamp <= ?", queryParams.StepSeconds/60, aggregation_query, r.indexTable) - case "component": - query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, component as groupBy, %s FROM %s WHERE timestamp >= ? AND timestamp <= ?", queryParams.StepSeconds/60, aggregation_query, r.indexTable) + case constants.ServiceName: + query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, serviceName as groupBy, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, aggregation_query, r.traceDB, r.indexTable) + case constants.HttpCode: + query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, httpCode as groupBy, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, aggregation_query, r.traceDB, r.indexTable) + case constants.HttpMethod: + query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, httpMethod as groupBy, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, aggregation_query, r.traceDB, r.indexTable) + case constants.HttpUrl: + query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, httpUrl as groupBy, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, aggregation_query, r.traceDB, r.indexTable) + case constants.HttpRoute: + query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, httpRoute as groupBy, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, aggregation_query, r.traceDB, r.indexTable) + case constants.HttpHost: + query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, httpHost as groupBy, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, aggregation_query, r.traceDB, r.indexTable) + case constants.DBName: + query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, dbName as groupBy, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, aggregation_query, r.traceDB, r.indexTable) + case constants.DBOperation: + query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, dbOperation as groupBy, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, aggregation_query, r.traceDB, r.indexTable) + case constants.OperationRequest: + query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, name as groupBy, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, aggregation_query, r.traceDB, r.indexTable) + case constants.MsgSystem: + query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, msgSystem as groupBy, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, aggregation_query, r.traceDB, r.indexTable) + case constants.MsgOperation: + query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, msgOperation as groupBy, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, aggregation_query, r.traceDB, r.indexTable) + case constants.DBSystem: + query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, dbSystem as groupBy, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, aggregation_query, r.traceDB, r.indexTable) + case constants.Component: + query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, component as groupBy, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, aggregation_query, r.traceDB, r.indexTable) default: - return nil, &model.ApiError{model.ErrorBadData, fmt.Errorf("groupBy type: %s not supported", queryParams.GroupBy)} + return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("groupBy type: %s not supported", queryParams.GroupBy)} } } else { - query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, %s FROM %s WHERE timestamp >= ? AND timestamp <= ?", queryParams.StepSeconds/60, aggregation_query, r.indexTable) + query = fmt.Sprintf("SELECT toStartOfInterval(timestamp, INTERVAL %d minute) as time, %s FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU", queryParams.StepSeconds/60, aggregation_query, r.traceDB, r.indexTable) } if len(queryParams.ServiceName) > 0 { @@ -2398,137 +2166,98 @@ func (r *ClickHouseReader) GetFilteredSpansAggregates(ctx context.Context, query args = buildFilterArrayQuery(ctx, excludeMap, queryParams.Operation, constants.OperationDB, &query, args) } if len(queryParams.MinDuration) != 0 { - query = query + " AND durationNano >= ?" - args = append(args, queryParams.MinDuration) + query = query + " AND durationNano >= @durationNanoMin" + args = append(args, clickhouse.Named("durationNanoMin", queryParams.MinDuration)) } if len(queryParams.MaxDuration) != 0 { - query = query + " AND durationNano <= ?" - args = append(args, queryParams.MaxDuration) + query = query + " AND durationNano <= @durationNanoMax" + args = append(args, clickhouse.Named("durationNanoMax", queryParams.MaxDuration)) } query = getStatusFilters(query, queryParams.Status, excludeMap) + if len(queryParams.Kind) != 0 { - query = query + " AND kind = ?" - args = append(args, queryParams.Kind) + query = query + " AND kind = @kind" + args = append(args, clickhouse.Named("kind", queryParams.Kind)) } - for _, item := range queryParams.Tags { - - if item.Operator == "in" { - for i, value := range item.Values { - if i == 0 && i == len(item.Values)-1 { - query += " AND has(tags, ?)" - } else if i == 0 && i != len(item.Values)-1 { - query += " AND (has(tags, ?)" - } else if i != 0 && i == len(item.Values)-1 { - query += " OR has(tags, ?))" - } else { - query += " OR has(tags, ?)" - } - args = append(args, fmt.Sprintf("%s:%s", item.Key, value)) - } - } else if item.Operator == "not in" { - for i, value := range item.Values { - if i == 0 && i == len(item.Values)-1 { - query += " AND NOT has(tags, ?)" - } else if i == 0 && i != len(item.Values)-1 { - query += " AND NOT (has(tags, ?)" - } else if i != 0 && i == len(item.Values)-1 { - query += " OR has(tags, ?))" - } else { - query += " OR has(tags, ?)" - } - args = append(args, fmt.Sprintf("%s:%s", item.Key, value)) - } - } else if item.Operator == "regex" { - if len(item.Values) != 1 { - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Regex tag operator should only have one value")} - } - query = query + " AND match(tagsValues[indexOf(tagsKeys, ?)], ?)" - args = append(args, item.Key) - args = append(args, item.Values[0]) - } else if item.Operator == "isnotnull" { - for range item.Values { - query = query + " AND has(tagsKeys, ?)" - args = append(args, item.Key) - } - } else { - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Tag Operator %s not supported", item.Operator)} - } - + args, errStatus := buildQueryWithTagParams(ctx, queryParams.Tags, &query, args) + if errStatus != nil { + return nil, errStatus } if queryParams.GroupBy != "" { switch queryParams.GroupBy { - case "serviceName": + case constants.ServiceName: query = query + " GROUP BY time, serviceName as groupBy ORDER BY time" - case "httpCode": + case constants.HttpCode: query = query + " GROUP BY time, httpCode as groupBy ORDER BY time" - case "httpMethod": + case constants.HttpMethod: query = query + " GROUP BY time, httpMethod as groupBy ORDER BY time" - case "httpUrl": + case constants.HttpUrl: query = query + " GROUP BY time, httpUrl as groupBy ORDER BY time" - case "httpRoute": + case constants.HttpRoute: query = query + " GROUP BY time, httpRoute as groupBy ORDER BY time" - case "httpHost": + case constants.HttpHost: query = query + " GROUP BY time, httpHost as groupBy ORDER BY time" - case "dbName": + case constants.DBName: query = query + " GROUP BY time, dbName as groupBy ORDER BY time" - case "dbOperation": + case constants.DBOperation: query = query + " GROUP BY time, dbOperation as groupBy ORDER BY time" - case "operation": + case constants.OperationRequest: query = query + " GROUP BY time, name as groupBy ORDER BY time" - case "msgSystem": + case constants.MsgSystem: query = query + " GROUP BY time, msgSystem as groupBy ORDER BY time" - case "msgOperation": + case constants.MsgOperation: query = query + " GROUP BY time, msgOperation as groupBy ORDER BY time" - case "dbSystem": + case constants.DBSystem: query = query + " GROUP BY time, dbSystem as groupBy ORDER BY time" - case "component": + case constants.Component: query = query + " GROUP BY time, component as groupBy ORDER BY time" default: - return nil, &model.ApiError{model.ErrorBadData, fmt.Errorf("groupBy type: %s not supported", queryParams.GroupBy)} + return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("groupBy type: %s not supported", queryParams.GroupBy)} } } else { query = query + " GROUP BY time ORDER BY time" } - err := r.db.Select(&SpanAggregatesDBResponseItems, query, args...) + err := r.db.Select(ctx, &SpanAggregatesDBResponseItems, query, args...) zap.S().Info(query) if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query")} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query")} } GetFilteredSpansAggregatesResponse := model.GetFilteredSpansAggregatesResponse{ Items: map[int64]model.SpanAggregatesResponseItem{}, } - for i, _ := range SpanAggregatesDBResponseItems { - - timeObj, _ := time.Parse(time.RFC3339Nano, SpanAggregatesDBResponseItems[i].Time) - SpanAggregatesDBResponseItems[i].Timestamp = int64(timeObj.UnixNano()) - SpanAggregatesDBResponseItems[i].Time = "" + for i := range SpanAggregatesDBResponseItems { + if SpanAggregatesDBResponseItems[i].Value == 0 { + SpanAggregatesDBResponseItems[i].Value = uint64(SpanAggregatesDBResponseItems[i].Float64Value) + } + SpanAggregatesDBResponseItems[i].Timestamp = int64(SpanAggregatesDBResponseItems[i].Time.UnixNano()) + SpanAggregatesDBResponseItems[i].FloatValue = float32(SpanAggregatesDBResponseItems[i].Value) if queryParams.AggregationOption == "rate_per_sec" { - SpanAggregatesDBResponseItems[i].Value = float32(SpanAggregatesDBResponseItems[i].Value) / float32(queryParams.StepSeconds) + SpanAggregatesDBResponseItems[i].FloatValue = float32(SpanAggregatesDBResponseItems[i].Value) / float32(queryParams.StepSeconds) } if responseElement, ok := GetFilteredSpansAggregatesResponse.Items[SpanAggregatesDBResponseItems[i].Timestamp]; !ok { - if queryParams.GroupBy != "" && SpanAggregatesDBResponseItems[i].GroupBy.String != "" { + if queryParams.GroupBy != "" && SpanAggregatesDBResponseItems[i].GroupBy != "" { GetFilteredSpansAggregatesResponse.Items[SpanAggregatesDBResponseItems[i].Timestamp] = model.SpanAggregatesResponseItem{ Timestamp: SpanAggregatesDBResponseItems[i].Timestamp, - GroupBy: map[string]float32{SpanAggregatesDBResponseItems[i].GroupBy.String: SpanAggregatesDBResponseItems[i].Value}, + GroupBy: map[string]float32{SpanAggregatesDBResponseItems[i].GroupBy: SpanAggregatesDBResponseItems[i].FloatValue}, } } else if queryParams.GroupBy == "" { GetFilteredSpansAggregatesResponse.Items[SpanAggregatesDBResponseItems[i].Timestamp] = model.SpanAggregatesResponseItem{ Timestamp: SpanAggregatesDBResponseItems[i].Timestamp, - Value: SpanAggregatesDBResponseItems[i].Value, + Value: SpanAggregatesDBResponseItems[i].FloatValue, } } } else { - if queryParams.GroupBy != "" && SpanAggregatesDBResponseItems[i].GroupBy.String != "" { - responseElement.GroupBy[SpanAggregatesDBResponseItems[i].GroupBy.String] = SpanAggregatesDBResponseItems[i].Value + if queryParams.GroupBy != "" && SpanAggregatesDBResponseItems[i].GroupBy != "" { + responseElement.GroupBy[SpanAggregatesDBResponseItems[i].GroupBy] = SpanAggregatesDBResponseItems[i].FloatValue } GetFilteredSpansAggregatesResponse.Items[SpanAggregatesDBResponseItems[i].Timestamp] = responseElement } @@ -2543,13 +2272,25 @@ func (r *ClickHouseReader) SetTTL(ctx context.Context, var req, tableName string switch params.Type { case constants.TraceTTL: - tableName = signozTraceTableName - req = fmt.Sprintf( - "ALTER TABLE default.%v MODIFY TTL toDateTime(timestamp) + INTERVAL %v SECOND DELETE", - tableName, params.DelDuration) - if len(params.ColdStorageVolume) > 0 { - req += fmt.Sprintf(", toDateTime(timestamp) + INTERVAL %v SECOND TO VOLUME '%s'", - params.ToColdStorageDuration, params.ColdStorageVolume) + tableNameArray := []string{signozTraceDBName + "." + signozTraceTableName, signozTraceDBName + "." + signozDurationMVTable, signozTraceDBName + "." + signozSpansTable, signozTraceDBName + "." + signozErrorIndexTable} + for _, tableName := range tableNameArray { + req = fmt.Sprintf( + "ALTER TABLE %v MODIFY TTL toDateTime(timestamp) + INTERVAL %v SECOND DELETE", + tableName, params.DelDuration) + if len(params.ColdStorageVolume) > 0 { + req += fmt.Sprintf(", toDateTime(timestamp) + INTERVAL %v SECOND TO VOLUME '%s'", + params.ToColdStorageDuration, params.ColdStorageVolume) + } + err := r.setColdStorage(ctx, tableName, params.ColdStorageVolume) + if err != nil { + return nil, err + } + + zap.S().Debugf("Executing TTL request: %s\n", req) + if err := r.db.Exec(ctx, req); err != nil { + zap.S().Error(fmt.Errorf("error while setting ttl. Err=%v", err)) + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error while setting ttl. Err=%v", err)} + } } case constants.MetricsTTL: @@ -2562,33 +2303,34 @@ func (r *ClickHouseReader) SetTTL(ctx context.Context, " + INTERVAL %v SECOND TO VOLUME '%s'", params.ToColdStorageDuration, params.ColdStorageVolume) } + zap.S().Debugf("Executing TTL request: %s\n", req) + if err := r.db.Exec(ctx, req); err != nil { + zap.S().Error(fmt.Errorf("error while setting ttl. Err=%v", err)) + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error while setting ttl. Err=%v", err)} + } default: - return nil, &model.ApiError{model.ErrorExec, - fmt.Errorf("error while setting ttl. ttl type should be , got %v", - params.Type)} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error while setting ttl. ttl type should be , got %v", + params.Type)} } + return &model.SetTTLResponseItem{Message: "move ttl has been successfully set up"}, nil +} + +func (r *ClickHouseReader) setColdStorage(ctx context.Context, tableName string, coldStorageVolume string) *model.ApiError { + // Set the storage policy for the required table. If it is already set, then setting it again // will not a problem. - if len(params.ColdStorageVolume) > 0 { + if len(coldStorageVolume) > 0 { policyReq := fmt.Sprintf("ALTER TABLE %s MODIFY SETTING storage_policy='tiered'", tableName) zap.S().Debugf("Executing Storage policy request: %s\n", policyReq) - if _, err := r.db.Exec(policyReq); err != nil { + if err := r.db.Exec(ctx, policyReq); err != nil { zap.S().Error(fmt.Errorf("error while setting storage policy. Err=%v", err)) - return nil, &model.ApiError{model.ErrorExec, - fmt.Errorf("error while setting storage policy. Err=%v", err)} + return &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error while setting storage policy. Err=%v", err)} } } - - zap.S().Debugf("Executing TTL request: %s\n", req) - if _, err := r.db.Exec(req); err != nil { - zap.S().Error(fmt.Errorf("error while setting ttl. Err=%v", err)) - return nil, &model.ApiError{model.ErrorExec, - fmt.Errorf("error while setting ttl. Err=%v", err)} - } - return &model.SetTTLResponseItem{Message: "move ttl has been successfully set up"}, nil + return nil } // GetDisks returns a list of disks {name, type} configured in clickhouse DB. @@ -2596,10 +2338,9 @@ func (r *ClickHouseReader) GetDisks(ctx context.Context) (*[]model.DiskItem, *mo diskItems := []model.DiskItem{} query := "SELECT name,type FROM system.disks" - if err := r.db.Select(&diskItems, query); err != nil { + if err := r.db.Select(ctx, &diskItems, query); err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, - fmt.Errorf("error while getting disks. Err=%v", err)} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error while getting disks. Err=%v", err)} } zap.S().Infof("Got response: %+v\n", diskItems) @@ -2639,32 +2380,39 @@ func (r *ClickHouseReader) GetTTL(ctx context.Context, ttlParams *model.GetTTLPa } getMetricsTTL := func() (*model.DBResponseTTL, *model.ApiError) { - var dbResp model.DBResponseTTL + var dbResp []model.DBResponseTTL - query := fmt.Sprintf("SELECT engine_full FROM system.tables WHERE name='%v'", signozSampleName) + query := fmt.Sprintf("SELECT engine_full FROM system.tables WHERE name='%v'", signozSampleTableName) - err := r.db.QueryRowx(query).StructScan(&dbResp) + err := r.db.Select(ctx, &dbResp, query) if err != nil { zap.S().Error(fmt.Errorf("error while getting ttl. Err=%v", err)) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("error while getting ttl. Err=%v", err)} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error while getting ttl. Err=%v", err)} + } + if len(dbResp) == 0 { + return nil, nil + } else { + return &dbResp[0], nil } - return &dbResp, nil } getTracesTTL := func() (*model.DBResponseTTL, *model.ApiError) { - var dbResp model.DBResponseTTL + var dbResp []model.DBResponseTTL - query := fmt.Sprintf("SELECT engine_full FROM system.tables WHERE name='%v'", signozTraceTableName) + query := fmt.Sprintf("SELECT engine_full FROM system.tables WHERE name='%v' AND database='%v'", signozTraceTableName, signozTraceDBName) - err := r.db.QueryRowx(query).StructScan(&dbResp) + err := r.db.Select(ctx, &dbResp, query) if err != nil { zap.S().Error(fmt.Errorf("error while getting ttl. Err=%v", err)) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("error while getting ttl. Err=%v", err)} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("error while getting ttl. Err=%v", err)} + } + if len(dbResp) == 0 { + return nil, nil + } else { + return &dbResp[0], nil } - - return &dbResp, nil } switch ttlParams.Type { @@ -2711,16 +2459,16 @@ func (r *ClickHouseReader) GetErrors(ctx context.Context, queryParams *model.Get var getErrorReponses []model.Error - query := fmt.Sprintf("SELECT exceptionType, exceptionMessage, count() AS exceptionCount, min(timestamp) as firstSeen, max(timestamp) as lastSeen, serviceName FROM %s WHERE timestamp >= ? AND timestamp <= ? GROUP BY serviceName, exceptionType, exceptionMessage", r.errorTable) - args := []interface{}{strconv.FormatInt(queryParams.Start.UnixNano(), 10), strconv.FormatInt(queryParams.End.UnixNano(), 10)} + query := fmt.Sprintf("SELECT exceptionType, exceptionMessage, count() AS exceptionCount, min(timestamp) as firstSeen, max(timestamp) as lastSeen, serviceName FROM %s.%s WHERE timestamp >= @timestampL AND timestamp <= @timestampU GROUP BY serviceName, exceptionType, exceptionMessage", r.traceDB, r.errorTable) + args := []interface{}{clickhouse.Named("timestampL", strconv.FormatInt(queryParams.Start.UnixNano(), 10)), clickhouse.Named("timestampU", strconv.FormatInt(queryParams.End.UnixNano(), 10))} - err := r.db.Select(&getErrorReponses, query, args...) + err := r.db.Select(ctx, &getErrorReponses, query, args...) zap.S().Info(query) if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query")} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query")} } return &getErrorReponses, nil @@ -2731,14 +2479,15 @@ func (r *ClickHouseReader) GetErrorForId(ctx context.Context, queryParams *model if queryParams.ErrorID == "" { zap.S().Debug("errorId missing from params") - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("ErrorID missing from params")} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("ErrorID missing from params")} } - var getErrorWithSpanReponse model.ErrorWithSpan + var getErrorWithSpanReponse []model.ErrorWithSpan - query := fmt.Sprintf("SELECT spanID, traceID, errorID, timestamp, serviceName, exceptionType, exceptionMessage, excepionStacktrace, exceptionEscaped, olderErrorId, newerErrorId FROM (SELECT *, lagInFrame(errorID) over w as olderErrorId, leadInFrame(errorID) over w as newerErrorId FROM %s window w as (ORDER BY exceptionType, serviceName, timestamp rows between unbounded preceding and unbounded following)) WHERE errorID = ?", r.errorTable) - args := []interface{}{queryParams.ErrorID} + // TODO: Optimize this query further + query := fmt.Sprintf("SELECT spanID, traceID, errorID, timestamp, serviceName, exceptionType, exceptionMessage, exceptionStacktrace, exceptionEscaped, olderErrorId, newerErrorId FROM (SELECT *, lagInFrame(toNullable(errorID)) over w as olderErrorId, leadInFrame(toNullable(errorID)) over w as newerErrorId FROM %s.%s window w as (ORDER BY exceptionType, serviceName, timestamp rows between unbounded preceding and unbounded following)) WHERE errorID = @errorID", r.traceDB, r.errorTable) + args := []interface{}{clickhouse.Named("errorID", queryParams.ErrorID)} - err := r.db.Get(&getErrorWithSpanReponse, query, args...) + err := r.db.Select(ctx, &getErrorWithSpanReponse, query, args...) zap.S().Info(query) @@ -2748,10 +2497,14 @@ func (r *ClickHouseReader) GetErrorForId(ctx context.Context, queryParams *model if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query")} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query")} } - return &getErrorWithSpanReponse, nil + if len(getErrorWithSpanReponse) > 0 { + return &getErrorWithSpanReponse[0], nil + } else { + return &model.ErrorWithSpan{}, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("Error ID not found")} + } } @@ -2759,25 +2512,140 @@ func (r *ClickHouseReader) GetErrorForType(ctx context.Context, queryParams *mod if queryParams.ErrorType == "" || queryParams.ServiceName == "" { zap.S().Debug("errorType/serviceName missing from params") - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("ErrorType/serviceName missing from params")} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("ErrorType/serviceName missing from params")} } - var getErrorWithSpanReponse model.ErrorWithSpan + var getErrorWithSpanReponse []model.ErrorWithSpan - query := fmt.Sprintf("SELECT spanID, traceID, errorID, timestamp , serviceName, exceptionType, exceptionMessage, excepionStacktrace, exceptionEscaped, newerErrorId, olderErrorId FROM (SELECT *, lagInFrame(errorID) over w as olderErrorId, leadInFrame(errorID) over w as newerErrorId FROM %s WHERE serviceName = ? AND exceptionType = ? window w as (ORDER BY timestamp rows between unbounded preceding and unbounded following)) limit 1", r.errorTable) - args := []interface{}{queryParams.ServiceName, queryParams.ErrorType} + // TODO: Optimize this query further + query := fmt.Sprintf("SELECT spanID, traceID, errorID, timestamp , serviceName, exceptionType, exceptionMessage, exceptionStacktrace, exceptionEscaped, newerErrorId, olderErrorId FROM (SELECT *, lagInFrame(errorID) over w as olderErrorId, leadInFrame(errorID) over w as newerErrorId FROM %s.%s WHERE serviceName = @serviceName AND exceptionType = @errorType window w as (ORDER BY timestamp DESC rows between unbounded preceding and unbounded following))", r.traceDB, r.errorTable) + args := []interface{}{clickhouse.Named("serviceName", queryParams.ServiceName), clickhouse.Named("errorType", queryParams.ErrorType)} - err := r.db.Get(&getErrorWithSpanReponse, query, args...) + err := r.db.Select(ctx, &getErrorWithSpanReponse, query, args...) zap.S().Info(query) - if err == sql.ErrNoRows { - return nil, nil - } if err != nil { zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{model.ErrorExec, fmt.Errorf("Error in processing sql query")} + return nil, &model.ApiError{Typ: model.ErrorExec, Err: fmt.Errorf("Error in processing sql query")} } - return &getErrorWithSpanReponse, nil + if len(getErrorWithSpanReponse) > 0 { + return &getErrorWithSpanReponse[0], nil + } else { + return nil, &model.ApiError{Typ: model.ErrorUnavailable, Err: fmt.Errorf("Error/Exception not found")} + } + +} + +func (r *ClickHouseReader) GetMetricAutocompleteTagKey(ctx context.Context, params *model.MetricAutocompleteTagParams) (*[]string, *model.ApiError) { + + var query string + var err error + var tagKeyList []string + var rows driver.Rows + + tagsWhereClause := "" + + for key, val := range params.MetricTags { + tagsWhereClause += fmt.Sprintf("AND JSONExtractString(labels,'%s') = '%s'", key, val) + } + // "select distinctTagKeys from (SELECT DISTINCT arrayJoin(tagKeys) distinctTagKeys from (SELECT DISTINCT(JSONExtractKeys(labels)) tagKeys from signoz_metrics.time_series WHERE JSONExtractString(labels,'__name__')='node_udp_queues')) WHERE distinctTagKeys ILIKE '%host%';" + if len(params.Match) != 0 { + query = fmt.Sprintf("select distinctTagKeys from (SELECT DISTINCT arrayJoin(tagKeys) distinctTagKeys from (SELECT DISTINCT(JSONExtractKeys(labels)) tagKeys from %s.%s WHERE JSONExtractString(labels,'__name__')=$1 %s)) WHERE distinctTagKeys ILIKE $2;", signozMetricDBName, signozTSTableName, tagsWhereClause) + + rows, err = r.db.Query(ctx, query, params.MetricName, fmt.Sprintf("%%%s%%", params.Match)) + + } else { + query = fmt.Sprintf("select distinctTagKeys from (SELECT DISTINCT arrayJoin(tagKeys) distinctTagKeys from (SELECT DISTINCT(JSONExtractKeys(labels)) tagKeys from %s.%s WHERE JSONExtractString(labels,'__name__')=$1 %s ));", signozMetricDBName, signozTSTableName, tagsWhereClause) + + rows, err = r.db.Query(ctx, query, params.MetricName) + } + + if err != nil { + zap.S().Error(err) + return nil, &model.ApiError{Typ: model.ErrorExec, Err: err} + } + + defer rows.Close() + var tagKey string + for rows.Next() { + if err := rows.Scan(&tagKey); err != nil { + return nil, &model.ApiError{Typ: model.ErrorExec, Err: err} + } + tagKeyList = append(tagKeyList, tagKey) + } + return &tagKeyList, nil +} + +func (r *ClickHouseReader) GetMetricAutocompleteTagValue(ctx context.Context, params *model.MetricAutocompleteTagParams) (*[]string, *model.ApiError) { + + var query string + var err error + var tagValueList []string + var rows driver.Rows + tagsWhereClause := "" + + for key, val := range params.MetricTags { + tagsWhereClause += fmt.Sprintf("AND JSONExtractString(labels,'%s') = '%s'", key, val) + } + + if len(params.Match) != 0 { + query = fmt.Sprintf("SELECT DISTINCT(JSONExtractString(labels, $1)) from %s.%s WHERE JSONExtractString(labels,'__name__')=$2 %s AND JSONExtractString(labels, $1) ILIKE $3;", signozMetricDBName, signozTSTableName, tagsWhereClause) + + rows, err = r.db.Query(ctx, query, params.TagKey, params.MetricName, fmt.Sprintf("%%%s%%", params.Match)) + + } else { + query = fmt.Sprintf("SELECT DISTINCT(JSONExtractString(labels, $1)) FROM %s.%s WHERE JSONExtractString(labels,'__name__')=$2 %s;", signozMetricDBName, signozTSTableName, tagsWhereClause) + rows, err = r.db.Query(ctx, query, params.TagKey, params.MetricName) + + } + + if err != nil { + zap.S().Error(err) + return nil, &model.ApiError{Typ: model.ErrorExec, Err: err} + } + + defer rows.Close() + var tagValue string + for rows.Next() { + if err := rows.Scan(&tagValue); err != nil { + return nil, &model.ApiError{Typ: model.ErrorExec, Err: err} + } + tagValueList = append(tagValueList, tagValue) + } + + return &tagValueList, nil +} + +func (r *ClickHouseReader) GetMetricAutocompleteMetricNames(ctx context.Context, matchText string) (*[]string, *model.ApiError) { + + var query string + var err error + var metricNameList []string + var rows driver.Rows + + if len(matchText) != 0 { + query = fmt.Sprintf("SELECT DISTINCT(JSONExtractString(labels,'__name__')) from %s.%s WHERE JSONExtractString(labels,'__name__') ILIKE $1;", signozMetricDBName, signozTSTableName) + rows, err = r.db.Query(ctx, query, fmt.Sprintf("%%%s%%", matchText)) + } else { + query = fmt.Sprintf("SELECT DISTINCT(JSONExtractString(labels,'__name__')) from %s.%s;", signozMetricDBName, signozTSTableName) + rows, err = r.db.Query(ctx, query) + } + + if err != nil { + zap.S().Error(err) + return nil, &model.ApiError{Typ: model.ErrorExec, Err: err} + } + + defer rows.Close() + var metricName string + for rows.Next() { + if err := rows.Scan(&metricName); err != nil { + return nil, &model.ApiError{Typ: model.ErrorExec, Err: err} + } + metricNameList = append(metricNameList, metricName) + } + + return &metricNameList, nil } diff --git a/pkg/query-service/app/clickhouseReader/reader_test.go b/pkg/query-service/app/clickhouseReader/reader_test.go new file mode 100644 index 0000000000..3cbc59b75c --- /dev/null +++ b/pkg/query-service/app/clickhouseReader/reader_test.go @@ -0,0 +1,29 @@ +package clickhouseReader + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type GetStatusFiltersTest struct { + query string + statusParams []string + excludeMap map[string]struct{} + expected string +} + +func TestGetStatusFilters(t *testing.T) { + assert := assert.New(t) + var tests = []GetStatusFiltersTest{ + {"", make([]string, 0), map[string]struct{}{}, ""}, + {"test", []string{"error"}, map[string]struct{}{}, "test AND hasError = true"}, + {"test", []string{"ok"}, map[string]struct{}{}, "test AND hasError = false"}, + {"test", []string{"error"}, map[string]struct{}{"status": {}}, "test AND hasError = false"}, + {"test", []string{"ok"}, map[string]struct{}{"status": {}}, "test AND hasError = true"}, + {"test", []string{"error", "ok"}, map[string]struct{}{}, "test"}, + } + for _, test := range tests { + assert.Equal(getStatusFilters(test.query, test.statusParams, test.excludeMap), test.expected) + } +} diff --git a/pkg/query-service/app/dashboards/model.go b/pkg/query-service/app/dashboards/model.go index 480fadf69d..9e1b3958d3 100644 --- a/pkg/query-service/app/dashboards/model.go +++ b/pkg/query-service/app/dashboards/model.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/google/uuid" "github.com/gosimple/slug" "github.com/jmoiron/sqlx" "go.signoz.io/query-service/model" @@ -35,7 +36,7 @@ func InitDB(dataSourceName string) (*sqlx.DB, error) { _, err = db.Exec(table_schema) if err != nil { - return nil, fmt.Errorf("Error in creating dashboard table: %v", err) + return nil, fmt.Errorf("Error in creating dashboard table: %s", err.Error()) } table_schema = `CREATE TABLE IF NOT EXISTS rules ( @@ -47,7 +48,7 @@ func InitDB(dataSourceName string) (*sqlx.DB, error) { _, err = db.Exec(table_schema) if err != nil { - return nil, fmt.Errorf("Error in creating rules table: %v", err) + return nil, fmt.Errorf("Error in creating rules table: %s", err.Error()) } table_schema = `CREATE TABLE IF NOT EXISTS notification_channels ( @@ -62,7 +63,7 @@ func InitDB(dataSourceName string) (*sqlx.DB, error) { _, err = db.Exec(table_schema) if err != nil { - return nil, fmt.Errorf("Error in creating notification_channles table: %v", err) + return nil, fmt.Errorf("Error in creating notification_channles table: %s", err.Error()) } return db, nil @@ -102,15 +103,14 @@ func (c *Data) Scan(src interface{}) error { } // CreateDashboard creates a new dashboard -func CreateDashboard(data *map[string]interface{}) (*Dashboard, *model.ApiError) { +func CreateDashboard(data map[string]interface{}) (*Dashboard, *model.ApiError) { dash := &Dashboard{ - Data: *data, + Data: data, } dash.CreatedAt = time.Now() dash.UpdatedAt = time.Now() dash.UpdateSlug() - // dash.Uuid = uuid.New().String() - dash.Uuid = dash.Data["uuid"].(string) + dash.Uuid = uuid.New().String() map_data, err := json.Marshal(dash.Data) if err != nil { @@ -135,7 +135,7 @@ func CreateDashboard(data *map[string]interface{}) (*Dashboard, *model.ApiError) return dash, nil } -func GetDashboards() (*[]Dashboard, *model.ApiError) { +func GetDashboards() ([]Dashboard, *model.ApiError) { dashboards := []Dashboard{} query := fmt.Sprintf("SELECT * FROM dashboards;") @@ -145,7 +145,7 @@ func GetDashboards() (*[]Dashboard, *model.ApiError) { return nil, &model.ApiError{Typ: model.ErrorExec, Err: err} } - return &dashboards, nil + return dashboards, nil } func DeleteDashboard(uuid string) *model.ApiError { @@ -182,9 +182,7 @@ func GetDashboard(uuid string) (*Dashboard, *model.ApiError) { return &dashboard, nil } -func UpdateDashboard(data *map[string]interface{}) (*Dashboard, *model.ApiError) { - - uuid := (*data)["uuid"].(string) +func UpdateDashboard(uuid string, data map[string]interface{}) (*Dashboard, *model.ApiError) { map_data, err := json.Marshal(data) if err != nil { @@ -198,7 +196,7 @@ func UpdateDashboard(data *map[string]interface{}) (*Dashboard, *model.ApiError) } dashboard.UpdatedAt = time.Now() - dashboard.Data = *data + dashboard.Data = data // db.Prepare("Insert into dashboards where") _, err = db.Exec("UPDATE dashboards SET updated_at=$1, data=$2 WHERE uuid=$3 ", dashboard.UpdatedAt, map_data, dashboard.Uuid) @@ -224,12 +222,7 @@ func (d *Dashboard) UpdateSlug() { func IsPostDataSane(data *map[string]interface{}) error { - val, ok := (*data)["uuid"] - if !ok || val == nil { - return fmt.Errorf("uuid not found in post data") - } - - val, ok = (*data)["title"] + val, ok := (*data)["title"] if !ok || val == nil { return fmt.Errorf("title not found in post data") } diff --git a/pkg/query-service/app/dashboards/provision.go b/pkg/query-service/app/dashboards/provision.go index b3a9e753b0..5bad8ae026 100644 --- a/pkg/query-service/app/dashboards/provision.go +++ b/pkg/query-service/app/dashboards/provision.go @@ -18,7 +18,6 @@ func readCurrentDir(dir string) error { list, _ := file.Readdirnames(0) // 0 to read all files and folders for _, filename := range list { - // fmt.Println(filename) zap.S().Info("Provisioning dashboard: ", filename) plan, err := ioutil.ReadFile(dir + "/" + filename) if err != nil { @@ -43,7 +42,7 @@ func readCurrentDir(dir string) error { continue } - _, apiErr = CreateDashboard(&data) + _, apiErr = CreateDashboard(data) if apiErr != nil { zap.S().Errorf("Creating Dashboards: Error in file: %s\t%s", filename, apiErr.Err) continue diff --git a/pkg/query-service/app/druidReader/reader.go b/pkg/query-service/app/druidReader/reader.go deleted file mode 100644 index 1c077466a6..0000000000 --- a/pkg/query-service/app/druidReader/reader.go +++ /dev/null @@ -1,203 +0,0 @@ -package druidReader - -import ( - "context" - "fmt" - "os" - - "github.com/jmoiron/sqlx" - "github.com/prometheus/prometheus/promql" - "github.com/prometheus/prometheus/util/stats" - "go.signoz.io/query-service/druidQuery" - "go.signoz.io/query-service/godruid" - "go.signoz.io/query-service/model" - am "go.signoz.io/query-service/integrations/alertManager" -) - -type DruidReader struct { - Client *godruid.Client - SqlClient *druidQuery.SqlClient - LocalDB *sqlx.DB -} - -func NewReader(localDB *sqlx.DB) *DruidReader { - - initialize() - druidClientUrl := os.Getenv("DruidClientUrl") - - client := godruid.Client{ - Url: druidClientUrl, - Debug: true, - } - - sqlClient := druidQuery.SqlClient{ - Url: druidClientUrl, - Debug: true, - } - return &DruidReader{ - Client: &client, - SqlClient: &sqlClient, - LocalDB: localDB, - } - -} - -func initialize() { - -} - -func (druid *DruidReader) GetQueryRangeResult(ctx context.Context, query *model.QueryRangeParams) (*promql.Result, *stats.QueryStats, *model.ApiError) { - - return nil, nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("Druid does not support metrics")} -} - -func (druid *DruidReader) GetInstantQueryMetricsResult(ctx context.Context, query *model.InstantQueryMetricsParams) (*promql.Result, *stats.QueryStats, *model.ApiError) { - - return nil, nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("Druid does not support metrics")} -} - -func (druid *DruidReader) DeleteChannel(id string) *model.ApiError { - return &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("Druid does not support notification channel for alerts")} -} - -func (druid *DruidReader) GetChannel(id string) (*model.ChannelItem, *model.ApiError) { - return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("Druid does not support notification channel for alerts")} -} -func (druid *DruidReader) GetChannels() (*[]model.ChannelItem, *model.ApiError) { - return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("Druid does not support notification channel for alerts")} -} -func (druid *DruidReader) CreateChannel(receiver *am.Receiver) (*am.Receiver, *model.ApiError) { - - return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("Druid does not support notification channel for alerts")} - -} -func (druid *DruidReader) EditChannel(receiver *am.Receiver, id string) (*am.Receiver, *model.ApiError) { - - return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("Druid does not support notification channel for alerts")} - -} - -func (druid *DruidReader) ListRulesFromProm() (*model.AlertDiscovery, *model.ApiError) { - - res := model.AlertDiscovery{} - return &res, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("Druid does not support getting rules for alerting")} -} -func (druid *DruidReader) GetRule(id string) (*model.RuleResponseItem, *model.ApiError) { - - return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("Druid does not support getting rules for alerting")} -} -func (druid *DruidReader) CreateRule(alert string) *model.ApiError { - - return &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("Druid does not support setting rules for alerting")} -} -func (druid *DruidReader) EditRule(alert string, id string) *model.ApiError { - - return &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("Druid does not support editing rules for alerting")} -} -func (druid *DruidReader) DeleteRule(id string) *model.ApiError { - - return &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("Druid does not support deleting rules for alerting")} -} - -func (druid *DruidReader) GetServiceOverview(ctx context.Context, query *model.GetServiceOverviewParams) (*[]model.ServiceOverviewItem, error) { - return druidQuery.GetServiceOverview(druid.SqlClient, query) -} - -func (druid *DruidReader) GetServices(ctx context.Context, query *model.GetServicesParams) (*[]model.ServiceItem, error) { - return druidQuery.GetServices(druid.SqlClient, query) -} - -func (druid *DruidReader) SearchSpans(ctx context.Context, query *model.SpanSearchParams) (*[]model.SearchSpansResult, error) { - return druidQuery.SearchSpans(druid.Client, query) -} - -func (druid *DruidReader) GetServiceDBOverview(ctx context.Context, query *model.GetServiceOverviewParams) (*[]model.ServiceDBOverviewItem, error) { - return druidQuery.GetServiceDBOverview(druid.SqlClient, query) -} - -func (druid *DruidReader) GetServiceExternalAvgDuration(ctx context.Context, query *model.GetServiceOverviewParams) (*[]model.ServiceExternalItem, error) { - return druidQuery.GetServiceExternalAvgDuration(druid.SqlClient, query) -} - -func (druid *DruidReader) GetServiceExternalErrors(ctx context.Context, query *model.GetServiceOverviewParams) (*[]model.ServiceExternalItem, error) { - return druidQuery.GetServiceExternalErrors(druid.SqlClient, query) -} - -func (druid *DruidReader) GetServiceExternal(ctx context.Context, query *model.GetServiceOverviewParams) (*[]model.ServiceExternalItem, error) { - return druidQuery.GetServiceExternal(druid.SqlClient, query) -} - -func (druid *DruidReader) GetTopEndpoints(ctx context.Context, query *model.GetTopEndpointsParams) (*[]model.TopEndpointsItem, error) { - return druidQuery.GetTopEndpoints(druid.SqlClient, query) -} - -func (druid *DruidReader) GetUsage(ctx context.Context, query *model.GetUsageParams) (*[]model.UsageItem, error) { - return druidQuery.GetUsage(druid.SqlClient, query) -} - -func (druid *DruidReader) GetOperations(ctx context.Context, serviceName string) (*[]string, error) { - return druidQuery.GetOperations(druid.SqlClient, serviceName) -} - -func (druid *DruidReader) GetTags(ctx context.Context, serviceName string) (*[]model.TagItem, error) { - return druidQuery.GetTags(druid.SqlClient, serviceName) -} - -func (druid *DruidReader) GetServicesList(ctx context.Context) (*[]string, error) { - return druidQuery.GetServicesList(druid.SqlClient) -} - -func (druid *DruidReader) SearchTraces(ctx context.Context, traceId string) (*[]model.SearchSpansResult, error) { - return druidQuery.SearchTraces(druid.Client, traceId) -} - -func (druid *DruidReader) GetServiceMapDependencies(ctx context.Context, query *model.GetServicesParams) (*[]model.ServiceMapDependencyResponseItem, error) { - return druidQuery.GetServiceMapDependencies(druid.SqlClient, query) -} -func (druid *DruidReader) SearchSpansAggregate(ctx context.Context, queryParams *model.SpanSearchAggregatesParams) ([]model.SpanSearchAggregatesResponseItem, error) { - return druidQuery.SearchSpansAggregate(druid.Client, queryParams) -} - -func (druid *DruidReader) SetTTL(_ context.Context, _ *model.TTLParams) (*model.SetTTLResponseItem, *model.ApiError) { - return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("druid does not support setting ttl configuration")} -} - -func (druid *DruidReader) GetDisks(ctx context.Context) (*[]model.DiskItem, *model.ApiError) { - return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("druid does not support getting disk list")} -} - -func (druid *DruidReader) GetTTL(_ context.Context, _ *model.GetTTLParams) (*model.GetTTLResponseItem, *model.ApiError) { - return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("druid does not support setting ttl configuration")} -} - -func (druid *DruidReader) GetSpanFilters(_ context.Context, _ *model.SpanFilterParams) (*model.SpanFiltersResponse, *model.ApiError) { - return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("druid does not support getting spanfilters")} -} - -func (druid *DruidReader) GetTagFilters(_ context.Context, _ *model.TagFilterParams) (*[]model.TagFilters, *model.ApiError) { - return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("druid does not support getting tagFilters")} -} - -func (druid *DruidReader) GetTagValues(_ context.Context, _ *model.TagFilterParams) (*[]model.TagValues, *model.ApiError) { - return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("druid does not support getting tagValues")} -} - -func (druid *DruidReader) GetFilteredSpans(_ context.Context, _ *model.GetFilteredSpansParams) (*model.GetFilterSpansResponse, *model.ApiError) { - return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("druid does not support getting FilteredSpans")} -} - -func (druid *DruidReader) GetFilteredSpansAggregates(_ context.Context, _ *model.GetFilteredSpanAggregatesParams) (*model.GetFilteredSpansAggregatesResponse, *model.ApiError) { - return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("druid does not support getting FilteredSpans")} -} - -func (druid *DruidReader) GetErrors(_ context.Context, _ *model.GetErrorsParams) (*[]model.Error, *model.ApiError) { - return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("druid does not support get error API")} -} - -func (druid *DruidReader) GetErrorForId(_ context.Context, _ *model.GetErrorParams) (*model.ErrorWithSpan, *model.ApiError) { - return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("druid does not support get error API")} -} - -func (druid *DruidReader) GetErrorForType(_ context.Context, _ *model.GetErrorParams) (*model.ErrorWithSpan, *model.ApiError) { - return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("druid does not support get error API")} -} diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index 771637935a..9b2c752749 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -3,6 +3,7 @@ package app import ( "context" "encoding/json" + "errors" "fmt" "io/ioutil" "net/http" @@ -12,11 +13,14 @@ import ( _ "github.com/mattn/go-sqlite3" "github.com/prometheus/prometheus/promql" "go.signoz.io/query-service/app/dashboards" - "go.signoz.io/query-service/dao/interfaces" + "go.signoz.io/query-service/app/parser" + "go.signoz.io/query-service/auth" + "go.signoz.io/query-service/constants" + "go.signoz.io/query-service/dao" + am "go.signoz.io/query-service/integrations/alertManager" "go.signoz.io/query-service/model" "go.signoz.io/query-service/telemetry" "go.signoz.io/query-service/version" - am "go.signoz.io/query-service/integrations/alertManager" "go.uber.org/zap" ) @@ -39,16 +43,19 @@ type APIHandler struct { basePath string apiPrefix string reader *Reader - relationalDB *interfaces.ModelDao + relationalDB dao.ModelDao + alertManager am.Manager ready func(http.HandlerFunc) http.HandlerFunc } // NewAPIHandler returns an APIHandler -func NewAPIHandler(reader *Reader, relationalDB *interfaces.ModelDao) (*APIHandler, error) { +func NewAPIHandler(reader *Reader, relationalDB dao.ModelDao) (*APIHandler, error) { + alertManager := am.New("") aH := &APIHandler{ reader: reader, relationalDB: relationalDB, + alertManager: alertManager, } aH.ready = aH.testReady @@ -105,7 +112,7 @@ type response struct { Error string `json:"error,omitempty"` } -func (aH *APIHandler) respondError(w http.ResponseWriter, apiErr *model.ApiError, data interface{}) { +func respondError(w http.ResponseWriter, apiErr *model.ApiError, data interface{}) { json := jsoniter.ConfigCompatibleWithStandardLibrary b, err := json.Marshal(&response{ Status: statusError, @@ -133,6 +140,10 @@ func (aH *APIHandler) respondError(w http.ResponseWriter, apiErr *model.ApiError code = http.StatusNotFound case model.ErrorNotImplemented: code = http.StatusNotImplemented + case model.ErrorUnauthorized: + code = http.StatusUnauthorized + case model.ErrorForbidden: + code = http.StatusForbidden default: code = http.StatusInternalServerError } @@ -144,7 +155,7 @@ func (aH *APIHandler) respondError(w http.ResponseWriter, apiErr *model.ApiError } } -func (aH *APIHandler) respond(w http.ResponseWriter, data interface{}) { +func writeHttpResponse(w http.ResponseWriter, data interface{}) { json := jsoniter.ConfigCompatibleWithStandardLibrary b, err := json.Marshal(&response{ Status: statusSuccess, @@ -162,65 +173,183 @@ func (aH *APIHandler) respond(w http.ResponseWriter, data interface{}) { zap.S().Error("msg", "error writing response", "bytesWritten", n, "err", err) } } +func (aH *APIHandler) RegisterMetricsRoutes(router *mux.Router) { + subRouter := router.PathPrefix("/api/v2/metrics").Subrouter() + subRouter.HandleFunc("/query_range", aH.queryRangeMetricsV2).Methods(http.MethodPost) + subRouter.HandleFunc("/autocomplete/list", aH.metricAutocompleteMetricName).Methods(http.MethodGet) + subRouter.HandleFunc("/autocomplete/tagKey", aH.metricAutocompleteTagKey).Methods(http.MethodGet) + subRouter.HandleFunc("/autocomplete/tagValue", aH.metricAutocompleteTagValue).Methods(http.MethodGet) +} + +func (aH *APIHandler) respond(w http.ResponseWriter, data interface{}) { + writeHttpResponse(w, data) +} + +func OpenAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + f(w, r) + } +} + +func ViewAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + user, err := auth.GetUserFromRequest(r) + if err != nil { + respondError(w, &model.ApiError{ + Typ: model.ErrorUnauthorized, + Err: err, + }, nil) + return + } + + if !(auth.IsViewer(user) || auth.IsEditor(user) || auth.IsAdmin(user)) { + respondError(w, &model.ApiError{ + Typ: model.ErrorForbidden, + Err: errors.New("API is accessible to viewers/editors/admins."), + }, nil) + return + } + f(w, r) + } +} + +func EditAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + user, err := auth.GetUserFromRequest(r) + if err != nil { + respondError(w, &model.ApiError{ + Typ: model.ErrorUnauthorized, + Err: err, + }, nil) + return + } + if !(auth.IsEditor(user) || auth.IsAdmin(user)) { + respondError(w, &model.ApiError{ + Typ: model.ErrorForbidden, + Err: errors.New("API is accessible to editors/admins."), + }, nil) + return + } + f(w, r) + } +} + +func SelfAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + user, err := auth.GetUserFromRequest(r) + if err != nil { + respondError(w, &model.ApiError{ + Typ: model.ErrorUnauthorized, + Err: err, + }, nil) + return + } + id := mux.Vars(r)["id"] + if !(auth.IsSelfAccessRequest(user, id) || auth.IsAdmin(user)) { + respondError(w, &model.ApiError{ + Typ: model.ErrorForbidden, + Err: errors.New("API is accessible for self access or to the admins."), + }, nil) + return + } + f(w, r) + } +} + +func AdminAccess(f func(http.ResponseWriter, *http.Request)) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + user, err := auth.GetUserFromRequest(r) + if err != nil { + respondError(w, &model.ApiError{ + Typ: model.ErrorUnauthorized, + Err: err, + }, nil) + return + } + if !auth.IsAdmin(user) { + respondError(w, &model.ApiError{ + Typ: model.ErrorForbidden, + Err: errors.New("API is accessible to admins only"), + }, nil) + return + } + f(w, r) + } +} // RegisterRoutes registers routes for this handler on the given router func (aH *APIHandler) RegisterRoutes(router *mux.Router) { - router.HandleFunc("/api/v1/query_range", aH.queryRangeMetrics).Methods(http.MethodGet) - router.HandleFunc("/api/v1/query", aH.queryMetrics).Methods(http.MethodGet) - router.HandleFunc("/api/v1/channels", aH.listChannels).Methods(http.MethodGet) - router.HandleFunc("/api/v1/channels/{id}", aH.getChannel).Methods(http.MethodGet) - router.HandleFunc("/api/v1/channels/{id}", aH.editChannel).Methods(http.MethodPut) - router.HandleFunc("/api/v1/channels/{id}", aH.deleteChannel).Methods(http.MethodDelete) - router.HandleFunc("/api/v1/channels", aH.createChannel).Methods(http.MethodPost) - router.HandleFunc("/api/v1/rules", aH.listRulesFromProm).Methods(http.MethodGet) - router.HandleFunc("/api/v1/rules/{id}", aH.getRule).Methods(http.MethodGet) - router.HandleFunc("/api/v1/rules", aH.createRule).Methods(http.MethodPost) - router.HandleFunc("/api/v1/rules/{id}", aH.editRule).Methods(http.MethodPut) - router.HandleFunc("/api/v1/rules/{id}", aH.deleteRule).Methods(http.MethodDelete) + router.HandleFunc("/api/v1/query_range", ViewAccess(aH.queryRangeMetrics)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/query", ViewAccess(aH.queryMetrics)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/channels", ViewAccess(aH.listChannels)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/channels/{id}", ViewAccess(aH.getChannel)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/channels/{id}", AdminAccess(aH.editChannel)).Methods(http.MethodPut) + router.HandleFunc("/api/v1/channels/{id}", AdminAccess(aH.deleteChannel)).Methods(http.MethodDelete) + router.HandleFunc("/api/v1/channels", EditAccess(aH.createChannel)).Methods(http.MethodPost) + router.HandleFunc("/api/v1/testChannel", EditAccess(aH.testChannel)).Methods(http.MethodPost) + router.HandleFunc("/api/v1/rules", ViewAccess(aH.listRulesFromProm)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/rules/{id}", ViewAccess(aH.getRule)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/rules", EditAccess(aH.createRule)).Methods(http.MethodPost) + router.HandleFunc("/api/v1/rules/{id}", EditAccess(aH.editRule)).Methods(http.MethodPut) + router.HandleFunc("/api/v1/rules/{id}", EditAccess(aH.deleteRule)).Methods(http.MethodDelete) - router.HandleFunc("/api/v1/dashboards", aH.getDashboards).Methods(http.MethodGet) - router.HandleFunc("/api/v1/dashboards", aH.createDashboards).Methods(http.MethodPost) - router.HandleFunc("/api/v1/dashboards/{uuid}", aH.getDashboard).Methods(http.MethodGet) - router.HandleFunc("/api/v1/dashboards/{uuid}", aH.updateDashboard).Methods(http.MethodPut) - router.HandleFunc("/api/v1/dashboards/{uuid}", aH.deleteDashboard).Methods(http.MethodDelete) + router.HandleFunc("/api/v1/dashboards", ViewAccess(aH.getDashboards)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/dashboards", EditAccess(aH.createDashboards)).Methods(http.MethodPost) + router.HandleFunc("/api/v1/dashboards/{uuid}", ViewAccess(aH.getDashboard)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/dashboards/{uuid}", EditAccess(aH.updateDashboard)).Methods(http.MethodPut) + router.HandleFunc("/api/v1/dashboards/{uuid}", EditAccess(aH.deleteDashboard)).Methods(http.MethodDelete) - router.HandleFunc("/api/v1/user", aH.user).Methods(http.MethodPost) - - router.HandleFunc("/api/v1/feedback", aH.submitFeedback).Methods(http.MethodPost) + router.HandleFunc("/api/v1/feedback", OpenAccess(aH.submitFeedback)).Methods(http.MethodPost) // router.HandleFunc("/api/v1/get_percentiles", aH.getApplicationPercentiles).Methods(http.MethodGet) - router.HandleFunc("/api/v1/services", aH.getServices).Methods(http.MethodGet) + router.HandleFunc("/api/v1/services", ViewAccess(aH.getServices)).Methods(http.MethodPost) router.HandleFunc("/api/v1/services/list", aH.getServicesList).Methods(http.MethodGet) - router.HandleFunc("/api/v1/service/overview", aH.getServiceOverview).Methods(http.MethodGet) - router.HandleFunc("/api/v1/service/dbOverview", aH.getServiceDBOverview).Methods(http.MethodGet) - router.HandleFunc("/api/v1/service/externalAvgDuration", aH.GetServiceExternalAvgDuration).Methods(http.MethodGet) - router.HandleFunc("/api/v1/service/externalErrors", aH.getServiceExternalErrors).Methods(http.MethodGet) - router.HandleFunc("/api/v1/service/external", aH.getServiceExternal).Methods(http.MethodGet) - router.HandleFunc("/api/v1/service/{service}/operations", aH.getOperations).Methods(http.MethodGet) - router.HandleFunc("/api/v1/service/top_endpoints", aH.getTopEndpoints).Methods(http.MethodGet) - router.HandleFunc("/api/v1/spans", aH.searchSpans).Methods(http.MethodGet) - router.HandleFunc("/api/v1/spans/aggregates", aH.searchSpansAggregates).Methods(http.MethodGet) - router.HandleFunc("/api/v1/tags", aH.searchTags).Methods(http.MethodGet) - router.HandleFunc("/api/v1/traces/{traceId}", aH.searchTraces).Methods(http.MethodGet) - router.HandleFunc("/api/v1/usage", aH.getUsage).Methods(http.MethodGet) - router.HandleFunc("/api/v1/serviceMapDependencies", aH.serviceMapDependencies).Methods(http.MethodGet) - router.HandleFunc("/api/v1/settings/ttl", aH.setTTL).Methods(http.MethodPost) - router.HandleFunc("/api/v1/settings/ttl", aH.getTTL).Methods(http.MethodGet) + router.HandleFunc("/api/v1/service/overview", ViewAccess(aH.getServiceOverview)).Methods(http.MethodPost) + router.HandleFunc("/api/v1/service/top_endpoints", ViewAccess(aH.getTopEndpoints)).Methods(http.MethodPost) + router.HandleFunc("/api/v1/traces/{traceId}", ViewAccess(aH.searchTraces)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/usage", ViewAccess(aH.getUsage)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/serviceMapDependencies", ViewAccess(aH.serviceMapDependencies)).Methods(http.MethodPost) + router.HandleFunc("/api/v1/settings/ttl", AdminAccess(aH.setTTL)).Methods(http.MethodPost) + router.HandleFunc("/api/v1/settings/ttl", ViewAccess(aH.getTTL)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/userPreferences", aH.setUserPreferences).Methods(http.MethodPost) - router.HandleFunc("/api/v1/userPreferences", aH.getUserPreferences).Methods(http.MethodGet) - router.HandleFunc("/api/v1/version", aH.getVersion).Methods(http.MethodGet) + router.HandleFunc("/api/v1/version", OpenAccess(aH.getVersion)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/getSpanFilters", aH.getSpanFilters).Methods(http.MethodPost) - router.HandleFunc("/api/v1/getTagFilters", aH.getTagFilters).Methods(http.MethodPost) - router.HandleFunc("/api/v1/getFilteredSpans", aH.getFilteredSpans).Methods(http.MethodPost) - router.HandleFunc("/api/v1/getFilteredSpans/aggregates", aH.getFilteredSpanAggregates).Methods(http.MethodPost) + router.HandleFunc("/api/v1/getSpanFilters", ViewAccess(aH.getSpanFilters)).Methods(http.MethodPost) + router.HandleFunc("/api/v1/getTagFilters", ViewAccess(aH.getTagFilters)).Methods(http.MethodPost) + router.HandleFunc("/api/v1/getFilteredSpans", ViewAccess(aH.getFilteredSpans)).Methods(http.MethodPost) + router.HandleFunc("/api/v1/getFilteredSpans/aggregates", ViewAccess(aH.getFilteredSpanAggregates)).Methods(http.MethodPost) - router.HandleFunc("/api/v1/getTagValues", aH.getTagValues).Methods(http.MethodPost) - router.HandleFunc("/api/v1/errors", aH.getErrors).Methods(http.MethodGet) - router.HandleFunc("/api/v1/errorWithId", aH.getErrorForId).Methods(http.MethodGet) - router.HandleFunc("/api/v1/errorWithType", aH.getErrorForType).Methods(http.MethodGet) + router.HandleFunc("/api/v1/getTagValues", ViewAccess(aH.getTagValues)).Methods(http.MethodPost) + router.HandleFunc("/api/v1/errors", ViewAccess(aH.getErrors)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/errorWithId", ViewAccess(aH.getErrorForId)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/errorWithType", ViewAccess(aH.getErrorForType)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/disks", aH.getDisks).Methods(http.MethodGet) + router.HandleFunc("/api/v1/disks", ViewAccess(aH.getDisks)).Methods(http.MethodGet) + + // === Authentication APIs === + router.HandleFunc("/api/v1/invite", AdminAccess(aH.inviteUser)).Methods(http.MethodPost) + router.HandleFunc("/api/v1/invite/{token}", OpenAccess(aH.getInvite)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/invite/{email}", AdminAccess(aH.revokeInvite)).Methods(http.MethodDelete) + router.HandleFunc("/api/v1/invite", AdminAccess(aH.listPendingInvites)).Methods(http.MethodGet) + + router.HandleFunc("/api/v1/register", OpenAccess(aH.registerUser)).Methods(http.MethodPost) + router.HandleFunc("/api/v1/login", OpenAccess(aH.loginUser)).Methods(http.MethodPost) + + router.HandleFunc("/api/v1/user", AdminAccess(aH.listUsers)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/user/{id}", SelfAccess(aH.getUser)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/user/{id}", SelfAccess(aH.editUser)).Methods(http.MethodPut) + router.HandleFunc("/api/v1/user/{id}", AdminAccess(aH.deleteUser)).Methods(http.MethodDelete) + + router.HandleFunc("/api/v1/rbac/role/{id}", SelfAccess(aH.getRole)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/rbac/role/{id}", AdminAccess(aH.editRole)).Methods(http.MethodPut) + + router.HandleFunc("/api/v1/org", AdminAccess(aH.getOrgs)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/org/{id}", AdminAccess(aH.getOrg)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/org/{id}", AdminAccess(aH.editOrg)).Methods(http.MethodPut) + router.HandleFunc("/api/v1/orgUsers/{id}", AdminAccess(aH.getOrgUsers)).Methods(http.MethodGet) + + router.HandleFunc("/api/v1/getResetPasswordToken/{id}", AdminAccess(aH.getResetPasswordToken)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/resetPassword", OpenAccess(aH.resetPassword)).Methods(http.MethodPost) + router.HandleFunc("/api/v1/changePassword/{id}", SelfAccess(aH.changePassword)).Methods(http.MethodPost) } func Intersection(a, b []int) (c []int) { @@ -242,16 +371,84 @@ func (aH *APIHandler) getRule(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] alertList, apiErrorObj := (*aH.reader).GetRule(id) if apiErrorObj != nil { - aH.respondError(w, apiErrorObj, nil) + respondError(w, apiErrorObj, nil) return } aH.respond(w, alertList) } +func (aH *APIHandler) metricAutocompleteMetricName(w http.ResponseWriter, r *http.Request) { + matchText := r.URL.Query().Get("match") + metricNameList, apiErrObj := (*aH.reader).GetMetricAutocompleteMetricNames(r.Context(), matchText) + + if apiErrObj != nil { + respondError(w, apiErrObj, nil) + return + } + aH.respond(w, metricNameList) + +} + +func (aH *APIHandler) metricAutocompleteTagKey(w http.ResponseWriter, r *http.Request) { + metricsAutocompleteTagKeyParams, apiErrorObj := parser.ParseMetricAutocompleteTagParams(r) + if apiErrorObj != nil { + respondError(w, apiErrorObj, nil) + return + } + + tagKeyList, apiErrObj := (*aH.reader).GetMetricAutocompleteTagKey(r.Context(), metricsAutocompleteTagKeyParams) + + if apiErrObj != nil { + respondError(w, apiErrObj, nil) + return + } + aH.respond(w, tagKeyList) +} + +func (aH *APIHandler) metricAutocompleteTagValue(w http.ResponseWriter, r *http.Request) { + metricsAutocompleteTagValueParams, apiErrorObj := parser.ParseMetricAutocompleteTagParams(r) + + if len(metricsAutocompleteTagValueParams.TagKey) == 0 { + apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("tagKey not present in params")} + respondError(w, apiErrObj, nil) + return + } + if apiErrorObj != nil { + respondError(w, apiErrorObj, nil) + return + } + + tagValueList, apiErrObj := (*aH.reader).GetMetricAutocompleteTagValue(r.Context(), metricsAutocompleteTagValueParams) + + if apiErrObj != nil { + respondError(w, apiErrObj, nil) + return + } + + aH.respond(w, tagValueList) +} + +func (aH *APIHandler) queryRangeMetricsV2(w http.ResponseWriter, r *http.Request) { + metricsQueryRangeParams, apiErrorObj := parser.ParseMetricQueryRangeParams(r) + + fmt.Println(metricsQueryRangeParams) + + if apiErrorObj != nil { + zap.S().Errorf(apiErrorObj.Err.Error()) + respondError(w, apiErrorObj, nil) + return + } + response_data := &model.QueryDataV2{ + ResultType: "matrix", + Result: nil, + } + aH.respond(w, response_data) +} + func (aH *APIHandler) listRulesFromProm(w http.ResponseWriter, r *http.Request) { alertList, apiErrorObj := (*aH.reader).ListRulesFromProm() if apiErrorObj != nil { - aH.respondError(w, apiErrorObj, nil) + respondError(w, apiErrorObj, nil) return } aH.respond(w, alertList) @@ -262,18 +459,18 @@ func (aH *APIHandler) getDashboards(w http.ResponseWriter, r *http.Request) { allDashboards, err := dashboards.GetDashboards() if err != nil { - aH.respondError(w, err, nil) + respondError(w, err, nil) return } tagsFromReq, ok := r.URL.Query()["tags"] if !ok || len(tagsFromReq) == 0 || tagsFromReq[0] == "" { - aH.respond(w, &allDashboards) + aH.respond(w, allDashboards) return } tags2Dash := make(map[string][]int) - for i := 0; i < len(*allDashboards); i++ { - tags, ok := (*allDashboards)[i].Data["tags"].([]interface{}) + for i := 0; i < len(allDashboards); i++ { + tags, ok := (allDashboards)[i].Data["tags"].([]interface{}) if !ok { continue } @@ -289,7 +486,7 @@ func (aH *APIHandler) getDashboards(w http.ResponseWriter, r *http.Request) { } - inter := make([]int, len(*allDashboards)) + inter := make([]int, len(allDashboards)) for i := range inter { inter[i] = i } @@ -300,11 +497,11 @@ func (aH *APIHandler) getDashboards(w http.ResponseWriter, r *http.Request) { filteredDashboards := []dashboards.Dashboard{} for _, val := range inter { - dash := (*allDashboards)[val] + dash := (allDashboards)[val] filteredDashboards = append(filteredDashboards, dash) } - aH.respond(w, &filteredDashboards) + aH.respond(w, filteredDashboards) } func (aH *APIHandler) deleteDashboard(w http.ResponseWriter, r *http.Request) { @@ -313,7 +510,7 @@ func (aH *APIHandler) deleteDashboard(w http.ResponseWriter, r *http.Request) { err := dashboards.DeleteDashboard(uuid) if err != nil { - aH.respondError(w, err, nil) + respondError(w, err, nil) return } @@ -328,24 +525,18 @@ func (aH *APIHandler) updateDashboard(w http.ResponseWriter, r *http.Request) { var postData map[string]interface{} err := json.NewDecoder(r.Body).Decode(&postData) if err != nil { - aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading request body") + respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading request body") return } err = dashboards.IsPostDataSane(&postData) if err != nil { - aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading request body") + respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading request body") return } - if postData["uuid"] != uuid { - aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("uuid in request param and uuid in request body do not match")}, "Error reading request body") - return - } - - dashboard, apiError := dashboards.UpdateDashboard(&postData) - + dashboard, apiError := dashboards.UpdateDashboard(uuid, postData) if apiError != nil { - aH.respondError(w, apiError, nil) + respondError(w, apiError, nil) return } @@ -360,7 +551,7 @@ func (aH *APIHandler) getDashboard(w http.ResponseWriter, r *http.Request) { dashboard, apiError := dashboards.GetDashboard(uuid) if apiError != nil { - aH.respondError(w, apiError, nil) + respondError(w, apiError, nil) return } @@ -371,21 +562,23 @@ func (aH *APIHandler) getDashboard(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) createDashboards(w http.ResponseWriter, r *http.Request) { var postData map[string]interface{} + err := json.NewDecoder(r.Body).Decode(&postData) if err != nil { - aH.respondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, "Error reading request body") + respondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, "Error reading request body") return } + err = dashboards.IsPostDataSane(&postData) if err != nil { - aH.respondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, "Error reading request body") + respondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, "Error reading request body") return } - dash, apiErr := dashboards.CreateDashboard(&postData) + dash, apiErr := dashboards.CreateDashboard(postData) if apiErr != nil { - aH.respondError(w, apiErr, nil) + respondError(w, apiErr, nil) return } @@ -399,7 +592,7 @@ func (aH *APIHandler) deleteRule(w http.ResponseWriter, r *http.Request) { apiErrorObj := (*aH.reader).DeleteRule(id) if apiErrorObj != nil { - aH.respondError(w, apiErrorObj, nil) + respondError(w, apiErrorObj, nil) return } @@ -412,14 +605,14 @@ func (aH *APIHandler) editRule(w http.ResponseWriter, r *http.Request) { var postData map[string]string err := json.NewDecoder(r.Body).Decode(&postData) if err != nil { - aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading request body") + respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading request body") return } apiErrorObj := (*aH.reader).EditRule(postData["data"], id) if apiErrorObj != nil { - aH.respondError(w, apiErrorObj, nil) + respondError(w, apiErrorObj, nil) return } @@ -431,7 +624,7 @@ func (aH *APIHandler) getChannel(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] channel, apiErrorObj := (*aH.reader).GetChannel(id) if apiErrorObj != nil { - aH.respondError(w, apiErrorObj, nil) + respondError(w, apiErrorObj, nil) return } aH.respond(w, channel) @@ -441,7 +634,7 @@ func (aH *APIHandler) deleteChannel(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] apiErrorObj := (*aH.reader).DeleteChannel(id) if apiErrorObj != nil { - aH.respondError(w, apiErrorObj, nil) + respondError(w, apiErrorObj, nil) return } aH.respond(w, "notification channel successfully deleted") @@ -450,12 +643,39 @@ func (aH *APIHandler) deleteChannel(w http.ResponseWriter, r *http.Request) { func (aH *APIHandler) listChannels(w http.ResponseWriter, r *http.Request) { channels, apiErrorObj := (*aH.reader).GetChannels() if apiErrorObj != nil { - aH.respondError(w, apiErrorObj, nil) + respondError(w, apiErrorObj, nil) return } aH.respond(w, channels) } +// testChannels sends test alert to all registered channels +func (aH *APIHandler) testChannel(w http.ResponseWriter, r *http.Request) { + + defer r.Body.Close() + body, err := ioutil.ReadAll(r.Body) + if err != nil { + zap.S().Errorf("Error in getting req body of testChannel API\n", err) + respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) + return + } + + receiver := &am.Receiver{} + if err := json.Unmarshal(body, receiver); err != nil { // Parse []byte to go struct pointer + zap.S().Errorf("Error in parsing req body of testChannel API\n", err) + respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) + return + } + + // send alert + apiErrorObj := aH.alertManager.TestReceiver(receiver) + if apiErrorObj != nil { + respondError(w, apiErrorObj, nil) + return + } + aH.respond(w, "test alert sent") +} + func (aH *APIHandler) editChannel(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] @@ -464,21 +684,21 @@ func (aH *APIHandler) editChannel(w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(r.Body) if err != nil { zap.S().Errorf("Error in getting req body of editChannel API\n", err) - aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) + respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) return } receiver := &am.Receiver{} if err := json.Unmarshal(body, receiver); err != nil { // Parse []byte to go struct pointer zap.S().Errorf("Error in parsing req body of editChannel API\n", err) - aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) + respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) return } _, apiErrorObj := (*aH.reader).EditChannel(receiver, id) if apiErrorObj != nil { - aH.respondError(w, apiErrorObj, nil) + respondError(w, apiErrorObj, nil) return } @@ -492,21 +712,21 @@ func (aH *APIHandler) createChannel(w http.ResponseWriter, r *http.Request) { body, err := ioutil.ReadAll(r.Body) if err != nil { zap.S().Errorf("Error in getting req body of createChannel API\n", err) - aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) + respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) return } receiver := &am.Receiver{} if err := json.Unmarshal(body, receiver); err != nil { // Parse []byte to go struct pointer zap.S().Errorf("Error in parsing req body of createChannel API\n", err) - aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) + respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) return } _, apiErrorObj := (*aH.reader).CreateChannel(receiver) if apiErrorObj != nil { - aH.respondError(w, apiErrorObj, nil) + respondError(w, apiErrorObj, nil) return } @@ -522,27 +742,29 @@ func (aH *APIHandler) createRule(w http.ResponseWriter, r *http.Request) { err := decoder.Decode(&postData) if err != nil { - aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) + respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) return } apiErrorObj := (*aH.reader).CreateRule(postData["data"]) if apiErrorObj != nil { - aH.respondError(w, apiErrorObj, nil) + respondError(w, apiErrorObj, nil) return } aH.respond(w, "rule successfully added") } +func (aH *APIHandler) queryRangeMetricsFromClickhouse(w http.ResponseWriter, r *http.Request) { +} func (aH *APIHandler) queryRangeMetrics(w http.ResponseWriter, r *http.Request) { query, apiErrorObj := parseQueryRangeRequest(r) if apiErrorObj != nil { - aH.respondError(w, apiErrorObj, nil) + respondError(w, apiErrorObj, nil) return } @@ -563,7 +785,7 @@ func (aH *APIHandler) queryRangeMetrics(w http.ResponseWriter, r *http.Request) res, qs, apiError := (*aH.reader).GetQueryRangeResult(ctx, query) if apiError != nil { - aH.respondError(w, apiError, nil) + respondError(w, apiError, nil) return } @@ -574,11 +796,11 @@ func (aH *APIHandler) queryRangeMetrics(w http.ResponseWriter, r *http.Request) if res.Err != nil { switch res.Err.(type) { case promql.ErrQueryCanceled: - aH.respondError(w, &model.ApiError{model.ErrorCanceled, res.Err}, nil) + respondError(w, &model.ApiError{model.ErrorCanceled, res.Err}, nil) case promql.ErrQueryTimeout: - aH.respondError(w, &model.ApiError{model.ErrorTimeout, res.Err}, nil) + respondError(w, &model.ApiError{model.ErrorTimeout, res.Err}, nil) } - aH.respondError(w, &model.ApiError{model.ErrorExec, res.Err}, nil) + respondError(w, &model.ApiError{model.ErrorExec, res.Err}, nil) } response_data := &model.QueryData{ @@ -596,7 +818,7 @@ func (aH *APIHandler) queryMetrics(w http.ResponseWriter, r *http.Request) { queryParams, apiErrorObj := parseInstantQueryMetricsRequest(r) if apiErrorObj != nil { - aH.respondError(w, apiErrorObj, nil) + respondError(w, apiErrorObj, nil) return } @@ -617,7 +839,7 @@ func (aH *APIHandler) queryMetrics(w http.ResponseWriter, r *http.Request) { res, qs, apiError := (*aH.reader).GetInstantQueryMetricsResult(ctx, queryParams) if apiError != nil { - aH.respondError(w, apiError, nil) + respondError(w, apiError, nil) return } @@ -628,11 +850,11 @@ func (aH *APIHandler) queryMetrics(w http.ResponseWriter, r *http.Request) { if res.Err != nil { switch res.Err.(type) { case promql.ErrQueryCanceled: - aH.respondError(w, &model.ApiError{model.ErrorCanceled, res.Err}, nil) + respondError(w, &model.ApiError{model.ErrorCanceled, res.Err}, nil) case promql.ErrQueryTimeout: - aH.respondError(w, &model.ApiError{model.ErrorTimeout, res.Err}, nil) + respondError(w, &model.ApiError{model.ErrorTimeout, res.Err}, nil) } - aH.respondError(w, &model.ApiError{model.ErrorExec, res.Err}, nil) + respondError(w, &model.ApiError{model.ErrorExec, res.Err}, nil) } response_data := &model.QueryData{ @@ -650,18 +872,18 @@ func (aH *APIHandler) submitFeedback(w http.ResponseWriter, r *http.Request) { var postData map[string]interface{} err := json.NewDecoder(r.Body).Decode(&postData) if err != nil { - aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading request body") + respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading request body") return } message, ok := postData["message"] if !ok { - aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("message not present in request body")}, "Error reading message from request body") + respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("message not present in request body")}, "Error reading message from request body") return } messageStr := fmt.Sprintf("%s", message) if len(messageStr) == 0 { - aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("empty message in request body")}, "empty message in request body") + respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("empty message in request body")}, "empty message in request body") return } @@ -675,71 +897,6 @@ func (aH *APIHandler) submitFeedback(w http.ResponseWriter, r *http.Request) { } -func (aH *APIHandler) user(w http.ResponseWriter, r *http.Request) { - - user, err := parseUser(r) - if err != nil { - if aH.handleError(w, err, http.StatusBadRequest) { - return - } - } - - telemetry.GetInstance().IdentifyUser(user) - data := map[string]interface{}{ - "name": user.Name, - "email": user.Email, - "organizationName": user.OrganizationName, - } - telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_USER, data) - -} - -func (aH *APIHandler) getOperations(w http.ResponseWriter, r *http.Request) { - - vars := mux.Vars(r) - serviceName := vars["service"] - - var err error - if len(serviceName) == 0 { - err = fmt.Errorf("service param not found") - } - if aH.handleError(w, err, http.StatusBadRequest) { - return - } - - result, err := (*aH.reader).GetOperations(context.Background(), serviceName) - if aH.handleError(w, err, http.StatusBadRequest) { - return - } - - aH.writeJSON(w, r, result) - -} - -func (aH *APIHandler) getServicesList(w http.ResponseWriter, r *http.Request) { - - result, err := (*aH.reader).GetServicesList(context.Background()) - if aH.handleError(w, err, http.StatusBadRequest) { - return - } - - aH.writeJSON(w, r, result) - -} - -func (aH *APIHandler) searchTags(w http.ResponseWriter, r *http.Request) { - - serviceName := r.URL.Query().Get("service") - - result, err := (*aH.reader).GetTags(context.Background(), serviceName) - if aH.handleError(w, err, http.StatusBadRequest) { - return - } - - aH.writeJSON(w, r, result) - -} - func (aH *APIHandler) getTopEndpoints(w http.ResponseWriter, r *http.Request) { query, err := parseGetTopEndpointsRequest(r) @@ -747,9 +904,9 @@ func (aH *APIHandler) getTopEndpoints(w http.ResponseWriter, r *http.Request) { return } - result, err := (*aH.reader).GetTopEndpoints(context.Background(), query) + result, apiErr := (*aH.reader).GetTopEndpoints(r.Context(), query) - if aH.handleError(w, err, http.StatusBadRequest) { + if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) { return } @@ -764,72 +921,7 @@ func (aH *APIHandler) getUsage(w http.ResponseWriter, r *http.Request) { return } - result, err := (*aH.reader).GetUsage(context.Background(), query) - if aH.handleError(w, err, http.StatusBadRequest) { - return - } - - aH.writeJSON(w, r, result) - -} - -func (aH *APIHandler) getServiceDBOverview(w http.ResponseWriter, r *http.Request) { - - query, err := parseGetServiceExternalRequest(r) - if aH.handleError(w, err, http.StatusBadRequest) { - return - } - - result, err := (*aH.reader).GetServiceDBOverview(context.Background(), query) - - if aH.handleError(w, err, http.StatusBadRequest) { - return - } - - aH.writeJSON(w, r, result) - -} - -func (aH *APIHandler) getServiceExternal(w http.ResponseWriter, r *http.Request) { - - query, err := parseGetServiceExternalRequest(r) - if aH.handleError(w, err, http.StatusBadRequest) { - return - } - - result, err := (*aH.reader).GetServiceExternal(context.Background(), query) - if aH.handleError(w, err, http.StatusBadRequest) { - return - } - - aH.writeJSON(w, r, result) - -} - -func (aH *APIHandler) GetServiceExternalAvgDuration(w http.ResponseWriter, r *http.Request) { - - query, err := parseGetServiceExternalRequest(r) - if aH.handleError(w, err, http.StatusBadRequest) { - return - } - - result, err := (*aH.reader).GetServiceExternalAvgDuration(context.Background(), query) - if aH.handleError(w, err, http.StatusBadRequest) { - return - } - - aH.writeJSON(w, r, result) - -} - -func (aH *APIHandler) getServiceExternalErrors(w http.ResponseWriter, r *http.Request) { - - query, err := parseGetServiceExternalRequest(r) - if aH.handleError(w, err, http.StatusBadRequest) { - return - } - - result, err := (*aH.reader).GetServiceExternalErrors(context.Background(), query) + result, err := (*aH.reader).GetUsage(r.Context(), query) if aH.handleError(w, err, http.StatusBadRequest) { return } @@ -845,8 +937,8 @@ func (aH *APIHandler) getServiceOverview(w http.ResponseWriter, r *http.Request) return } - result, err := (*aH.reader).GetServiceOverview(context.Background(), query) - if aH.handleError(w, err, http.StatusBadRequest) { + result, apiErr := (*aH.reader).GetServiceOverview(r.Context(), query) + if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) { return } @@ -861,8 +953,8 @@ func (aH *APIHandler) getServices(w http.ResponseWriter, r *http.Request) { return } - result, err := (*aH.reader).GetServices(context.Background(), query) - if aH.handleError(w, err, http.StatusBadRequest) { + result, apiErr := (*aH.reader).GetServices(r.Context(), query) + if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) { return } @@ -882,7 +974,7 @@ func (aH *APIHandler) serviceMapDependencies(w http.ResponseWriter, r *http.Requ return } - result, err := (*aH.reader).GetServiceMapDependencies(context.Background(), query) + result, err := (*aH.reader).GetServiceMapDependencies(r.Context(), query) if aH.handleError(w, err, http.StatusBadRequest) { return } @@ -890,12 +982,23 @@ func (aH *APIHandler) serviceMapDependencies(w http.ResponseWriter, r *http.Requ aH.writeJSON(w, r, result) } +func (aH *APIHandler) getServicesList(w http.ResponseWriter, r *http.Request) { + + result, err := (*aH.reader).GetServicesList(r.Context()) + if aH.handleError(w, err, http.StatusBadRequest) { + return + } + + aH.writeJSON(w, r, result) + +} + func (aH *APIHandler) searchTraces(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) traceId := vars["traceId"] - result, err := (*aH.reader).SearchTraces(context.Background(), traceId) + result, err := (*aH.reader).SearchTraces(r.Context(), traceId) if aH.handleError(w, err, http.StatusBadRequest) { return } @@ -910,7 +1013,7 @@ func (aH *APIHandler) getErrors(w http.ResponseWriter, r *http.Request) { if aH.handleError(w, err, http.StatusBadRequest) { return } - result, apiErr := (*aH.reader).GetErrors(context.Background(), query) + result, apiErr := (*aH.reader).GetErrors(r.Context(), query) if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) { return } @@ -925,7 +1028,7 @@ func (aH *APIHandler) getErrorForId(w http.ResponseWriter, r *http.Request) { if aH.handleError(w, err, http.StatusBadRequest) { return } - result, apiErr := (*aH.reader).GetErrorForId(context.Background(), query) + result, apiErr := (*aH.reader).GetErrorForId(r.Context(), query) if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) { return } @@ -940,7 +1043,7 @@ func (aH *APIHandler) getErrorForType(w http.ResponseWriter, r *http.Request) { if aH.handleError(w, err, http.StatusBadRequest) { return } - result, apiErr := (*aH.reader).GetErrorForType(context.Background(), query) + result, apiErr := (*aH.reader).GetErrorForType(r.Context(), query) if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) { return } @@ -949,38 +1052,6 @@ func (aH *APIHandler) getErrorForType(w http.ResponseWriter, r *http.Request) { } -func (aH *APIHandler) searchSpansAggregates(w http.ResponseWriter, r *http.Request) { - - query, err := parseSearchSpanAggregatesRequest(r) - if aH.handleError(w, err, http.StatusBadRequest) { - return - } - - result, err := (*aH.reader).SearchSpansAggregate(context.Background(), query) - if aH.handleError(w, err, http.StatusBadRequest) { - return - } - - aH.writeJSON(w, r, result) -} - -func (aH *APIHandler) searchSpans(w http.ResponseWriter, r *http.Request) { - - query, err := parseSpanSearchRequest(r) - if aH.handleError(w, err, http.StatusBadRequest) { - return - } - - // result, err := druidQuery.SearchSpans(aH.client, query) - result, err := (*aH.reader).SearchSpans(context.Background(), query) - - if aH.handleError(w, err, http.StatusBadRequest) { - return - } - - aH.writeJSON(w, r, result) -} - func (aH *APIHandler) getSpanFilters(w http.ResponseWriter, r *http.Request) { query, err := parseSpanFilterRequestBody(r) @@ -988,7 +1059,7 @@ func (aH *APIHandler) getSpanFilters(w http.ResponseWriter, r *http.Request) { return } - result, apiErr := (*aH.reader).GetSpanFilters(context.Background(), query) + result, apiErr := (*aH.reader).GetSpanFilters(r.Context(), query) if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) { return @@ -1004,7 +1075,7 @@ func (aH *APIHandler) getFilteredSpans(w http.ResponseWriter, r *http.Request) { return } - result, apiErr := (*aH.reader).GetFilteredSpans(context.Background(), query) + result, apiErr := (*aH.reader).GetFilteredSpans(r.Context(), query) if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) { return @@ -1020,7 +1091,7 @@ func (aH *APIHandler) getFilteredSpanAggregates(w http.ResponseWriter, r *http.R return } - result, apiErr := (*aH.reader).GetFilteredSpansAggregates(context.Background(), query) + result, apiErr := (*aH.reader).GetFilteredSpansAggregates(r.Context(), query) if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) { return @@ -1036,7 +1107,7 @@ func (aH *APIHandler) getTagFilters(w http.ResponseWriter, r *http.Request) { return } - result, apiErr := (*aH.reader).GetTagFilters(context.Background(), query) + result, apiErr := (*aH.reader).GetTagFilters(r.Context(), query) if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) { return @@ -1052,7 +1123,7 @@ func (aH *APIHandler) getTagValues(w http.ResponseWriter, r *http.Request) { return } - result, apiErr := (*aH.reader).GetTagValues(context.Background(), query) + result, apiErr := (*aH.reader).GetTagValues(r.Context(), query) if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) { return @@ -1067,6 +1138,7 @@ func (aH *APIHandler) setTTL(w http.ResponseWriter, r *http.Request) { return } + // Context is not used here as TTL is long duration operation which needs to converted to async result, apiErr := (*aH.reader).SetTTL(context.Background(), ttlParams) if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) { return @@ -1082,7 +1154,7 @@ func (aH *APIHandler) getTTL(w http.ResponseWriter, r *http.Request) { return } - result, apiErr := (*aH.reader).GetTTL(context.Background(), ttlParams) + result, apiErr := (*aH.reader).GetTTL(r.Context(), ttlParams) if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) { return } @@ -1099,43 +1171,428 @@ func (aH *APIHandler) getDisks(w http.ResponseWriter, r *http.Request) { aH.writeJSON(w, r, result) } -func (aH *APIHandler) getUserPreferences(w http.ResponseWriter, r *http.Request) { - - result, apiError := (*aH.relationalDB).FetchUserPreference(context.Background()) - if apiError != nil { - aH.respondError(w, apiError, "Error from Fetch Dao") - return - } - - aH.writeJSON(w, r, result) +func (aH *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) { + version := version.GetVersion() + aH.writeJSON(w, r, map[string]string{"version": version}) } -func (aH *APIHandler) setUserPreferences(w http.ResponseWriter, r *http.Request) { - userParams, err := parseUserPreferences(r) +// inviteUser is used to invite a user. It is used by an admin api. +func (aH *APIHandler) inviteUser(w http.ResponseWriter, r *http.Request) { + req, err := parseInviteRequest(r) if aH.handleError(w, err, http.StatusBadRequest) { return } - apiErr := (*aH.relationalDB).UpdateUserPreferece(context.Background(), userParams) - if apiErr != nil && aH.handleError(w, apiErr.Err, http.StatusInternalServerError) { + ctx := auth.AttachJwtToContext(context.Background(), r) + resp, err := auth.Invite(ctx, req) + if err != nil { + respondError(w, &model.ApiError{Err: err, Typ: model.ErrorInternal}, nil) + return + } + aH.writeJSON(w, r, resp) +} + +// getInvite returns the invite object details for the given invite token. We do not need to +// protect this API because invite token itself is meant to be private. +func (aH *APIHandler) getInvite(w http.ResponseWriter, r *http.Request) { + token := mux.Vars(r)["token"] + + resp, err := auth.GetInvite(context.Background(), token) + if err != nil { + respondError(w, &model.ApiError{Err: err, Typ: model.ErrorNotFound}, nil) + return + } + aH.writeJSON(w, r, resp) +} + +// revokeInvite is used to revoke an invite. +func (aH *APIHandler) revokeInvite(w http.ResponseWriter, r *http.Request) { + email := mux.Vars(r)["email"] + + ctx := auth.AttachJwtToContext(context.Background(), r) + if err := auth.RevokeInvite(ctx, email); err != nil { + respondError(w, &model.ApiError{Err: err, Typ: model.ErrorInternal}, nil) + return + } + aH.writeJSON(w, r, map[string]string{"data": "invite revoked successfully"}) +} + +// listPendingInvites is used to list the pending invites. +func (aH *APIHandler) listPendingInvites(w http.ResponseWriter, r *http.Request) { + + ctx := context.Background() + invites, err := dao.DB().GetInvites(ctx) + if err != nil { + respondError(w, err, nil) + return + } + + // TODO(Ahsan): Querying org name based on orgId for each invite is not a good idea. Either + // we should include org name field in the invite table, or do a join query. + var resp []*model.InvitationResponseObject + for _, inv := range invites { + + org, apiErr := dao.DB().GetOrg(ctx, inv.OrgId) + if apiErr != nil { + respondError(w, apiErr, nil) + } + resp = append(resp, &model.InvitationResponseObject{ + Name: inv.Name, + Email: inv.Email, + Token: inv.Token, + CreatedAt: inv.CreatedAt, + Role: inv.Role, + Organization: org.Name, + }) + } + aH.writeJSON(w, r, resp) +} + +func (aH *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) { + req, err := parseRegisterRequest(r) + if aH.handleError(w, err, http.StatusBadRequest) { + return + } + + apiErr := auth.Register(context.Background(), req) + if apiErr != nil { + respondError(w, apiErr, nil) + return + } + + aH.writeJSON(w, r, map[string]string{"data": "user registered successfully"}) +} + +func (aH *APIHandler) loginUser(w http.ResponseWriter, r *http.Request) { + req, err := parseLoginRequest(r) + if aH.handleError(w, err, http.StatusBadRequest) { + return + } + + // c, err := r.Cookie("refresh-token") + // if err != nil { + // if err != http.ErrNoCookie { + // w.WriteHeader(http.StatusBadRequest) + // return + // } + // } + + // if c != nil { + // req.RefreshToken = c.Value + // } + + resp, err := auth.Login(context.Background(), req) + if aH.handleError(w, err, http.StatusUnauthorized) { + return + } + + // http.SetCookie(w, &http.Cookie{ + // Name: "refresh-token", + // Value: resp.RefreshJwt, + // Expires: time.Unix(resp.RefreshJwtExpiry, 0), + // HttpOnly: true, + // }) + + aH.writeJSON(w, r, resp) +} + +func (aH *APIHandler) listUsers(w http.ResponseWriter, r *http.Request) { + users, err := dao.DB().GetUsers(context.Background()) + if err != nil { + zap.S().Debugf("[listUsers] Failed to query list of users, err: %v", err) + respondError(w, err, nil) + return + } + // mask the password hash + for i := range users { + users[i].Password = "" + } + aH.writeJSON(w, r, users) +} + +func (aH *APIHandler) getUser(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + + ctx := context.Background() + user, err := dao.DB().GetUser(ctx, id) + if err != nil { + zap.S().Debugf("[getUser] Failed to query user, err: %v", err) + respondError(w, err, "Failed to get user") + return + } + if user == nil { + respondError(w, &model.ApiError{ + Typ: model.ErrorInternal, + Err: errors.New("User not found"), + }, nil) + return + } + + // No need to send password hash for the user object. + user.Password = "" + aH.writeJSON(w, r, user) +} + +// editUser only changes the user's Name and ProfilePictureURL. It is intentionally designed +// to not support update of orgId, Password, createdAt for the sucurity reasons. +func (aH *APIHandler) editUser(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + + update, err := parseUserRequest(r) + if aH.handleError(w, err, http.StatusBadRequest) { + return + } + + ctx := context.Background() + old, apiErr := dao.DB().GetUser(ctx, id) + if apiErr != nil { + zap.S().Debugf("[editUser] Failed to query user, err: %v", err) + respondError(w, apiErr, nil) + return + } + + if len(update.Name) > 0 { + old.Name = update.Name + } + if len(update.ProfilePirctureURL) > 0 { + old.ProfilePirctureURL = update.ProfilePirctureURL + } + + _, apiErr = dao.DB().EditUser(ctx, &model.User{ + Id: old.Id, + Name: old.Name, + OrgId: old.OrgId, + Email: old.Email, + Password: old.Password, + CreatedAt: old.CreatedAt, + ProfilePirctureURL: old.ProfilePirctureURL, + }) + if apiErr != nil { + respondError(w, apiErr, nil) + return + } + aH.writeJSON(w, r, map[string]string{"data": "user updated successfully"}) +} + +func (aH *APIHandler) deleteUser(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + + // Query for the user's group, and the admin's group. If the user belongs to the admin group + // and is the last user then don't let the deletion happen. Otherwise, the system will become + // admin less and hence inaccessible. + ctx := context.Background() + user, apiErr := dao.DB().GetUser(ctx, id) + if apiErr != nil { + respondError(w, apiErr, "Failed to get user's group") + return + } + + if user == nil { + respondError(w, &model.ApiError{ + Typ: model.ErrorNotFound, + Err: errors.New("User not found"), + }, nil) + return + } + + adminGroup, apiErr := dao.DB().GetGroupByName(ctx, constants.AdminGroup) + if apiErr != nil { + respondError(w, apiErr, "Failed to get admin group") + return + } + adminUsers, apiErr := dao.DB().GetUsersByGroup(ctx, adminGroup.Id) + if apiErr != nil { + respondError(w, apiErr, "Failed to get admin group users") + return + } + + if user.GroupId == adminGroup.Id && len(adminUsers) == 1 { + respondError(w, &model.ApiError{ + Typ: model.ErrorInternal, + Err: errors.New("cannot delete the last admin user")}, nil) + return + } + + err := dao.DB().DeleteUser(ctx, id) + if err != nil { + respondError(w, err, "Failed to delete user") + return + } + aH.writeJSON(w, r, map[string]string{"data": "user deleted successfully"}) +} + +func (aH *APIHandler) getRole(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + + user, err := dao.DB().GetUser(context.Background(), id) + if err != nil { + respondError(w, err, "Failed to get user's group") + return + } + if user == nil { + respondError(w, &model.ApiError{ + Typ: model.ErrorNotFound, + Err: errors.New("No user found"), + }, nil) + return + } + group, err := dao.DB().GetGroup(context.Background(), user.GroupId) + if err != nil { + respondError(w, err, "Failed to get group") + return + } + + aH.writeJSON(w, r, &model.UserRole{UserId: id, GroupName: group.Name}) +} + +func (aH *APIHandler) editRole(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + + req, err := parseUserRoleRequest(r) + if aH.handleError(w, err, http.StatusBadRequest) { + return + } + + ctx := context.Background() + newGroup, apiErr := dao.DB().GetGroupByName(ctx, req.GroupName) + if apiErr != nil { + respondError(w, apiErr, "Failed to get user's group") + return + } + + if newGroup == nil { + respondError(w, apiErr, "Specified group is not present") + return + } + + user, apiErr := dao.DB().GetUser(ctx, id) + if apiErr != nil { + respondError(w, apiErr, "Failed to fetch user group") + return + } + + // Make sure that the request is not demoting the last admin user. + if user.GroupId == auth.AuthCacheObj.AdminGroupId { + adminUsers, apiErr := dao.DB().GetUsersByGroup(ctx, auth.AuthCacheObj.AdminGroupId) + if apiErr != nil { + respondError(w, apiErr, "Failed to fetch adminUsers") + return + } + + if len(adminUsers) == 1 { + respondError(w, &model.ApiError{ + Err: errors.New("Cannot demote the last admin"), + Typ: model.ErrorInternal}, nil) + return + } + } + + apiErr = dao.DB().UpdateUserGroup(context.Background(), user.Id, newGroup.Id) + if apiErr != nil { + respondError(w, apiErr, "Failed to add user to group") + return + } + aH.writeJSON(w, r, map[string]string{"data": "user group updated successfully"}) +} + +func (aH *APIHandler) getOrgs(w http.ResponseWriter, r *http.Request) { + orgs, apiErr := dao.DB().GetOrgs(context.Background()) + if apiErr != nil { + respondError(w, apiErr, "Failed to fetch orgs from the DB") + return + } + aH.writeJSON(w, r, orgs) +} + +func (aH *APIHandler) getOrg(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + org, apiErr := dao.DB().GetOrg(context.Background(), id) + if apiErr != nil { + respondError(w, apiErr, "Failed to fetch org from the DB") + return + } + aH.writeJSON(w, r, org) +} + +func (aH *APIHandler) editOrg(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + req, err := parseEditOrgRequest(r) + if aH.handleError(w, err, http.StatusBadRequest) { + return + } + + req.Id = id + if apiErr := dao.DB().EditOrg(context.Background(), req); apiErr != nil { + respondError(w, apiErr, "Failed to update org in the DB") return } data := map[string]interface{}{ - "hasOptedUpdates": userParams.HasOptedUpdates, - "isAnonymous": userParams.IsAnonymous, + "hasOptedUpdates": req.HasOptedUpdates, + "isAnonymous": req.IsAnonymous, + "organizationName": req.Name, } - telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_USER_PREFERENCES, data) - aH.writeJSON(w, r, map[string]string{"data": "user preferences set successfully"}) + telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_ORG_SETTINGS, data) + aH.writeJSON(w, r, map[string]string{"data": "org updated successfully"}) } -func (aH *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) { +func (aH *APIHandler) getOrgUsers(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + users, apiErr := dao.DB().GetUsersByOrg(context.Background(), id) + if apiErr != nil { + respondError(w, apiErr, "Failed to fetch org users from the DB") + return + } + // mask the password hash + for i := range users { + users[i].Password = "" + } + aH.writeJSON(w, r, users) +} - version := version.GetVersion() +func (aH *APIHandler) getResetPasswordToken(w http.ResponseWriter, r *http.Request) { + id := mux.Vars(r)["id"] + resp, err := auth.CreateResetPasswordToken(context.Background(), id) + if err != nil { + respondError(w, &model.ApiError{ + Typ: model.ErrorInternal, + Err: err}, "Failed to create reset token entry in the DB") + return + } + aH.writeJSON(w, r, resp) +} - aH.writeJSON(w, r, map[string]string{"version": version}) +func (aH *APIHandler) resetPassword(w http.ResponseWriter, r *http.Request) { + req, err := parseResetPasswordRequest(r) + if aH.handleError(w, err, http.StatusBadRequest) { + return + } + + if err := auth.ResetPassword(context.Background(), req); err != nil { + zap.S().Debugf("resetPassword failed, err: %v\n", err) + if aH.handleError(w, err, http.StatusInternalServerError) { + return + } + + } + aH.writeJSON(w, r, map[string]string{"data": "password reset successfully"}) +} + +func (aH *APIHandler) changePassword(w http.ResponseWriter, r *http.Request) { + req, err := parseChangePasswordRequest(r) + if aH.handleError(w, err, http.StatusBadRequest) { + return + } + + if err := auth.ChangePassword(context.Background(), req); err != nil { + if aH.handleError(w, err, http.StatusInternalServerError) { + return + } + + } + aH.writeJSON(w, r, map[string]string{"data": "password changed successfully"}) } // func (aH *APIHandler) getApplicationPercentiles(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/query-service/app/interface.go b/pkg/query-service/app/interface.go index 8a979090d1..422f57ba41 100644 --- a/pkg/query-service/app/interface.go +++ b/pkg/query-service/app/interface.go @@ -5,8 +5,8 @@ import ( "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/util/stats" - "go.signoz.io/query-service/model" am "go.signoz.io/query-service/integrations/alertManager" + "go.signoz.io/query-service/model" ) type Reader interface { @@ -24,17 +24,11 @@ type Reader interface { GetInstantQueryMetricsResult(ctx context.Context, query *model.InstantQueryMetricsParams) (*promql.Result, *stats.QueryStats, *model.ApiError) GetQueryRangeResult(ctx context.Context, query *model.QueryRangeParams) (*promql.Result, *stats.QueryStats, *model.ApiError) - GetServiceOverview(ctx context.Context, query *model.GetServiceOverviewParams) (*[]model.ServiceOverviewItem, error) - GetServices(ctx context.Context, query *model.GetServicesParams) (*[]model.ServiceItem, error) + GetServiceOverview(ctx context.Context, query *model.GetServiceOverviewParams) (*[]model.ServiceOverviewItem, *model.ApiError) + GetServices(ctx context.Context, query *model.GetServicesParams) (*[]model.ServiceItem, *model.ApiError) // GetApplicationPercentiles(ctx context.Context, query *model.ApplicationPercentileParams) ([]godruid.Timeseries, error) - GetServiceDBOverview(ctx context.Context, query *model.GetServiceOverviewParams) (*[]model.ServiceDBOverviewItem, error) - GetServiceExternalAvgDuration(ctx context.Context, query *model.GetServiceOverviewParams) (*[]model.ServiceExternalItem, error) - GetServiceExternalErrors(ctx context.Context, query *model.GetServiceOverviewParams) (*[]model.ServiceExternalItem, error) - GetServiceExternal(ctx context.Context, query *model.GetServiceOverviewParams) (*[]model.ServiceExternalItem, error) - GetTopEndpoints(ctx context.Context, query *model.GetTopEndpointsParams) (*[]model.TopEndpointsItem, error) + GetTopEndpoints(ctx context.Context, query *model.GetTopEndpointsParams) (*[]model.TopEndpointsItem, *model.ApiError) GetUsage(ctx context.Context, query *model.GetUsageParams) (*[]model.UsageItem, error) - GetOperations(ctx context.Context, serviceName string) (*[]string, error) - GetTags(ctx context.Context, serviceName string) (*[]model.TagItem, error) GetServicesList(ctx context.Context) (*[]string, error) GetServiceMapDependencies(ctx context.Context, query *model.GetServicesParams) (*[]model.ServiceMapDependencyResponseItem, error) GetTTL(ctx context.Context, ttlParams *model.GetTTLParams) (*model.GetTTLResponseItem, *model.ApiError) @@ -52,10 +46,12 @@ type Reader interface { GetErrorForId(ctx context.Context, params *model.GetErrorParams) (*model.ErrorWithSpan, *model.ApiError) GetErrorForType(ctx context.Context, params *model.GetErrorParams) (*model.ErrorWithSpan, *model.ApiError) // Search Interfaces - SearchSpansAggregate(ctx context.Context, queryParams *model.SpanSearchAggregatesParams) ([]model.SpanSearchAggregatesResponseItem, error) - SearchSpans(ctx context.Context, query *model.SpanSearchParams) (*[]model.SearchSpansResult, error) SearchTraces(ctx context.Context, traceID string) (*[]model.SearchSpansResult, error) // Setter Interfaces SetTTL(ctx context.Context, ttlParams *model.TTLParams) (*model.SetTTLResponseItem, *model.ApiError) + + GetMetricAutocompleteMetricNames(ctx context.Context, matchText string) (*[]string, *model.ApiError) + GetMetricAutocompleteTagKey(ctx context.Context, params *model.MetricAutocompleteTagParams) (*[]string, *model.ApiError) + GetMetricAutocompleteTagValue(ctx context.Context, params *model.MetricAutocompleteTagParams) (*[]string, *model.ApiError) } diff --git a/pkg/query-service/app/parser.go b/pkg/query-service/app/parser.go index ae9abc5204..1f189a5541 100644 --- a/pkg/query-service/app/parser.go +++ b/pkg/query-service/app/parser.go @@ -7,30 +7,22 @@ import ( "math" "net/http" "strconv" - "strings" "time" + "github.com/gorilla/mux" promModel "github.com/prometheus/common/model" + "go.signoz.io/query-service/auth" "go.signoz.io/query-service/constants" "go.signoz.io/query-service/model" - "go.uber.org/zap" ) -var allowedDimesions = []string{"calls", "duration"} - var allowedFunctions = []string{"count", "ratePerSec", "sum", "avg", "min", "max", "p50", "p90", "p95", "p99"} -var allowedAggregations = map[string][]string{ - "calls": {"count", "rate_per_sec"}, - "duration": {"avg", "p50", "p95", "p90", "p99", "min", "max", "sum"}, -} - func parseUser(r *http.Request) (*model.User, error) { var user model.User - err := json.NewDecoder(r.Body).Decode(&user) - if err != nil { + if err := json.NewDecoder(r.Body).Decode(&user); err != nil { return nil, err } if len(user.Email) == 0 { @@ -41,30 +33,27 @@ func parseUser(r *http.Request) (*model.User, error) { } func parseGetTopEndpointsRequest(r *http.Request) (*model.GetTopEndpointsParams, error) { - startTime, err := parseTime("start", r) - if err != nil { - return nil, err - } - endTime, err := parseTime("end", r) + var postData *model.GetTopEndpointsParams + err := json.NewDecoder(r.Body).Decode(&postData) + if err != nil { return nil, err } - serviceName := r.URL.Query().Get("service") - if len(serviceName) == 0 { + postData.Start, err = parseTimeStr(postData.StartTime, "start") + if err != nil { + return nil, err + } + postData.End, err = parseTimeMinusBufferStr(postData.EndTime, "end") + if err != nil { + return nil, err + } + + if len(postData.ServiceName) == 0 { return nil, errors.New("serviceName param missing in query") } - getTopEndpointsParams := model.GetTopEndpointsParams{ - StartTime: startTime.Format(time.RFC3339Nano), - EndTime: endTime.Format(time.RFC3339Nano), - ServiceName: serviceName, - Start: startTime, - End: endTime, - } - - return &getTopEndpointsParams, nil - + return postData, nil } func parseMetricsTime(s string) (time.Time, error) { @@ -99,7 +88,7 @@ func parseInstantQueryMetricsRequest(r *http.Request) (*model.InstantQueryMetric var err error ts, err = parseMetricsTime(t) if err != nil { - return nil, &model.ApiError{model.ErrorBadData, err} + return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err} } } else { ts = time.Now() @@ -117,32 +106,32 @@ func parseQueryRangeRequest(r *http.Request) (*model.QueryRangeParams, *model.Ap start, err := parseMetricsTime(r.FormValue("start")) if err != nil { - return nil, &model.ApiError{model.ErrorBadData, err} + return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err} } end, err := parseMetricsTime(r.FormValue("end")) if err != nil { - return nil, &model.ApiError{model.ErrorBadData, err} + return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err} } if end.Before(start) { err := errors.New("end timestamp must not be before start time") - return nil, &model.ApiError{model.ErrorBadData, err} + return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err} } step, err := parseMetricsDuration(r.FormValue("step")) if err != nil { - return nil, &model.ApiError{model.ErrorBadData, err} + return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err} } if step <= 0 { err := errors.New("zero or negative query resolution step widths are not accepted. Try a positive integer") - return nil, &model.ApiError{model.ErrorBadData, err} + return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err} } // For safety, limit the number of returned points per timeseries. // This is sufficient for 60s resolution for a week or 1h resolution for a year. if end.Sub(start)/step > 11000 { err := errors.New("exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)") - return nil, &model.ApiError{model.ErrorBadData, err} + return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err} } queryRangeParams := model.QueryRangeParams{ @@ -192,102 +181,48 @@ func parseGetUsageRequest(r *http.Request) (*model.GetUsageParams, error) { } -func parseGetServiceExternalRequest(r *http.Request) (*model.GetServiceOverviewParams, error) { - startTime, err := parseTime("start", r) - if err != nil { - return nil, err - } - endTime, err := parseTime("end", r) - if err != nil { - return nil, err - } - - stepStr := r.URL.Query().Get("step") - if len(stepStr) == 0 { - return nil, errors.New("step param missing in query") - } - stepInt, err := strconv.Atoi(stepStr) - if err != nil { - return nil, errors.New("step param is not in correct format") - } - - serviceName := r.URL.Query().Get("service") - if len(serviceName) == 0 { - return nil, errors.New("serviceName param missing in query") - } - - getServiceOverviewParams := model.GetServiceOverviewParams{ - Start: startTime, - StartTime: startTime.Format(time.RFC3339Nano), - End: endTime, - EndTime: endTime.Format(time.RFC3339Nano), - ServiceName: serviceName, - Period: fmt.Sprintf("PT%dM", stepInt/60), - StepSeconds: stepInt, - } - - return &getServiceOverviewParams, nil - -} - func parseGetServiceOverviewRequest(r *http.Request) (*model.GetServiceOverviewParams, error) { - startTime, err := parseTime("start", r) - if err != nil { - return nil, err - } - endTime, err := parseTime("end", r) + + var postData *model.GetServiceOverviewParams + err := json.NewDecoder(r.Body).Decode(&postData) + if err != nil { return nil, err } - stepStr := r.URL.Query().Get("step") - if len(stepStr) == 0 { - return nil, errors.New("step param missing in query") - } - stepInt, err := strconv.Atoi(stepStr) + postData.Start, err = parseTimeStr(postData.StartTime, "start") if err != nil { - return nil, errors.New("step param is not in correct format") + return nil, err + } + postData.End, err = parseTimeMinusBufferStr(postData.EndTime, "end") + if err != nil { + return nil, err } - serviceName := r.URL.Query().Get("service") - if len(serviceName) == 0 { - return nil, errors.New("serviceName param missing in query") - } - - getServiceOverviewParams := model.GetServiceOverviewParams{ - Start: startTime, - StartTime: startTime.Format(time.RFC3339Nano), - End: endTime, - EndTime: endTime.Format(time.RFC3339Nano), - ServiceName: serviceName, - Period: fmt.Sprintf("PT%dM", stepInt/60), - StepSeconds: stepInt, - } - - return &getServiceOverviewParams, nil - + postData.Period = fmt.Sprintf("PT%dM", postData.StepSeconds/60) + return postData, nil } func parseGetServicesRequest(r *http.Request) (*model.GetServicesParams, error) { - startTime, err := parseTime("start", r) - if err != nil { - return nil, err - } - endTime, err := parseTime("end", r) + var postData *model.GetServicesParams + err := json.NewDecoder(r.Body).Decode(&postData) + if err != nil { return nil, err } - getServicesParams := model.GetServicesParams{ - Start: startTime, - StartTime: startTime.Format(time.RFC3339Nano), - End: endTime, - EndTime: endTime.Format(time.RFC3339Nano), - Period: int(endTime.Unix() - startTime.Unix()), + postData.Start, err = parseTimeStr(postData.StartTime, "start") + if err != nil { + return nil, err + } + postData.End, err = parseTimeMinusBufferStr(postData.EndTime, "end") + if err != nil { + return nil, err } - return &getServicesParams, nil + postData.Period = int(postData.End.Unix() - postData.Start.Unix()) + return postData, nil } func DoesExistInSlice(item string, list []string) bool { @@ -299,189 +234,6 @@ func DoesExistInSlice(item string, list []string) bool { return false } -func parseSearchSpanAggregatesRequest(r *http.Request) (*model.SpanSearchAggregatesParams, error) { - - startTime, err := parseTime("start", r) - if err != nil { - return nil, err - } - endTime, err := parseTime("end", r) - if err != nil { - return nil, err - } - - startTimeStr := startTime.Format(time.RFC3339Nano) - endTimeStr := endTime.Format(time.RFC3339Nano) - // fmt.Println(startTimeStr) - - stepStr := r.URL.Query().Get("step") - if len(stepStr) == 0 { - return nil, errors.New("step param missing in query") - } - - stepInt, err := strconv.Atoi(stepStr) - if err != nil { - return nil, errors.New("step param is not in correct format") - } - - granPeriod := fmt.Sprintf("PT%dM", stepInt/60) - dimension := r.URL.Query().Get("dimension") - if len(dimension) == 0 { - return nil, errors.New("dimension param missing in query") - } else { - if !DoesExistInSlice(dimension, allowedDimesions) { - return nil, errors.New(fmt.Sprintf("given dimension: %s is not allowed in query", dimension)) - } - } - - aggregationOption := r.URL.Query().Get("aggregation_option") - if len(aggregationOption) == 0 { - return nil, errors.New("Aggregation Option missing in query params") - } else { - if !DoesExistInSlice(aggregationOption, allowedAggregations[dimension]) { - return nil, errors.New(fmt.Sprintf("given aggregation option: %s is not allowed with dimension: %s", aggregationOption, dimension)) - } - } - - params := &model.SpanSearchAggregatesParams{ - Start: startTime, - End: endTime, - Intervals: fmt.Sprintf("%s/%s", startTimeStr, endTimeStr), - GranOrigin: startTimeStr, - GranPeriod: granPeriod, - StepSeconds: stepInt, - Dimension: dimension, - AggregationOption: aggregationOption, - } - - serviceName := r.URL.Query().Get("service") - if len(serviceName) != 0 { - // return nil, errors.New("serviceName param missing in query") - params.ServiceName = serviceName - } - operationName := r.URL.Query().Get("operation") - if len(operationName) != 0 { - params.OperationName = operationName - // Escaping new line chars to avoid CWE-117 - operationName = strings.Replace(operationName, "\n", "", -1) - operationName = strings.Replace(operationName, "\r", "", -1) - zap.S().Debug("Operation Name: ", operationName) - } - - kind := r.URL.Query().Get("kind") - if len(kind) != 0 { - params.Kind = kind - // Escaping new line chars to avoid CWE-117 - kind = strings.Replace(kind, "\n", "", -1) - kind = strings.Replace(kind, "\r", "", -1) - zap.S().Debug("Kind: ", kind) - } - - minDuration, err := parseTimestamp("minDuration", r) - if err == nil { - params.MinDuration = *minDuration - } - maxDuration, err := parseTimestamp("maxDuration", r) - if err == nil { - params.MaxDuration = *maxDuration - } - - tags, err := parseTags("tags", r) - if err != nil { - return nil, err - } - if len(*tags) != 0 { - params.Tags = *tags - } - - return params, nil -} - -func parseSpanSearchRequest(r *http.Request) (*model.SpanSearchParams, error) { - - startTime, err := parseTime("start", r) - if err != nil { - return nil, err - } - endTime, err := parseTimeMinusBuffer("end", r) - if err != nil { - return nil, err - } - - startTimeStr := startTime.Format(time.RFC3339Nano) - endTimeStr := endTime.Format(time.RFC3339Nano) - // fmt.Println(startTimeStr) - params := &model.SpanSearchParams{ - Intervals: fmt.Sprintf("%s/%s", startTimeStr, endTimeStr), - Start: startTime, - End: endTime, - Limit: 100, - Order: "descending", - } - - serviceName := r.URL.Query().Get("service") - if len(serviceName) != 0 { - // return nil, errors.New("serviceName param missing in query") - params.ServiceName = serviceName - } - operationName := r.URL.Query().Get("operation") - if len(operationName) != 0 { - params.OperationName = operationName - // Escaping new line chars to avoid CWE-117 - operationName = strings.Replace(operationName, "\n", "", -1) - operationName = strings.Replace(operationName, "\r", "", -1) - zap.S().Debug("Operation Name: ", operationName) - } - - kind := r.URL.Query().Get("kind") - if len(kind) != 0 { - params.Kind = kind - // Escaping new line chars to avoid CWE-117 - kind = strings.Replace(kind, "\n", "", -1) - kind = strings.Replace(kind, "\r", "", -1) - zap.S().Debug("Kind: ", kind) - } - - minDuration, err := parseTimestamp("minDuration", r) - if err == nil { - params.MinDuration = *minDuration - } - maxDuration, err := parseTimestamp("maxDuration", r) - if err == nil { - params.MaxDuration = *maxDuration - } - - limitStr := r.URL.Query().Get("limit") - if len(limitStr) != 0 { - limit, err := strconv.ParseInt(limitStr, 10, 64) - if err != nil { - return nil, errors.New("Limit param is not in correct format") - } - params.Limit = limit - } else { - params.Limit = 100 - } - - offsetStr := r.URL.Query().Get("offset") - if len(offsetStr) != 0 { - offset, err := strconv.ParseInt(offsetStr, 10, 64) - if err != nil { - return nil, errors.New("Offset param is not in correct format") - } - params.Offset = offset - } - - tags, err := parseTags("tags", r) - if err != nil { - return nil, err - } - if len(*tags) != 0 { - params.Tags = *tags - } - - return params, nil -} - func parseSpanFilterRequestBody(r *http.Request) (*model.SpanFilterParams, error) { var postData *model.SpanFilterParams @@ -522,7 +274,7 @@ func parseFilteredSpansRequest(r *http.Request) (*model.GetFilteredSpansParams, } if postData.Limit == 0 { - postData.Limit = 100 + postData.Limit = 10 } return postData, nil @@ -694,96 +446,6 @@ func parseErrorsRequest(r *http.Request) (*model.GetErrorsParams, error) { return params, nil } -func fetchArrayValues(param string, r *http.Request) []string { - valueStr := r.URL.Query().Get(param) - var values []string - if len(valueStr) == 0 { - return values - } - err := json.Unmarshal([]byte(valueStr), &values) - if err != nil { - zap.S().Error("Error in parsing service params", zap.Error(err)) - } - return values -} - -func parseTags(param string, r *http.Request) (*[]model.TagQuery, error) { - - tags := new([]model.TagQuery) - tagsStr := r.URL.Query().Get(param) - - if len(tagsStr) == 0 { - return tags, nil - } - err := json.Unmarshal([]byte(tagsStr), tags) - if err != nil { - zap.S().Error("Error in parsig tags", zap.Error(err)) - return nil, fmt.Errorf("error in parsing %s ", param) - } - // zap.S().Info("Tags: ", *tags) - - return tags, nil -} - -func parseTagsV2(param string, r *http.Request) (*[]model.TagQueryV2, error) { - - tags := new([]model.TagQueryV2) - tagsStr := r.URL.Query().Get(param) - - if len(tagsStr) == 0 { - return tags, nil - } - err := json.Unmarshal([]byte(tagsStr), tags) - if err != nil { - zap.S().Error("Error in parsig tags", zap.Error(err)) - return nil, fmt.Errorf("error in parsing %s ", param) - } - // zap.S().Info("Tags: ", *tags) - - return tags, nil -} - -func parseApplicationPercentileRequest(r *http.Request) (*model.ApplicationPercentileParams, error) { - - startTime, err := parseTime("start", r) - if err != nil { - return nil, err - } - endTime, err := parseTime("end", r) - if err != nil { - return nil, err - } - - stepStr := r.URL.Query().Get("step") - if len(stepStr) == 0 { - return nil, errors.New("step param missing in query") - } - - serviceName := r.URL.Query().Get("service") - if len(serviceName) == 0 { - return nil, errors.New("serviceName param missing in query") - } - - startTimeStr := startTime.Format(time.RFC3339Nano) - endTimeStr := endTime.Format(time.RFC3339Nano) - - params := &model.ApplicationPercentileParams{ - ServiceName: serviceName, - GranOrigin: startTimeStr, - Intervals: fmt.Sprintf("%s/%s", startTimeStr, endTimeStr), - } - - stepInt, err := strconv.Atoi(stepStr) - if err != nil { - return nil, errors.New("step param is not in correct format") - } - - params.SetGranPeriod(stepInt) - - return params, nil - -} - func parseTimeStr(timeStr string, param string) (*time.Time, error) { if len(timeStr) == 0 { @@ -864,25 +526,6 @@ func parseTimeMinusBuffer(param string, r *http.Request) (*time.Time, error) { } -func parseTimestamp(param string, r *http.Request) (*string, error) { - timeStr := r.URL.Query().Get(param) - if len(timeStr) == 0 { - return nil, fmt.Errorf("%s param missing in query", param) - } - - // timeUnix, err := strconv.ParseInt(timeStr, 10, 64) - // if err != nil || len(timeStr) == 0 { - // return nil, fmt.Errorf("%s param is not in correct timestamp format", param) - // } - - return &timeStr, nil - -} -func parseSetRulesRequest(r *http.Request) (string, *model.ApiError) { - - return "", nil -} - func parseTTLParams(r *http.Request) (*model.TTLParams, error) { // make sure either of the query params are present @@ -921,9 +564,9 @@ func parseTTLParams(r *http.Request) (*model.TTLParams, error) { return &model.TTLParams{ Type: typeTTL, - DelDuration: durationParsed.Seconds(), + DelDuration: int64(durationParsed.Seconds()), ColdStorageVolume: coldStorage, - ToColdStorageDuration: toColdParsed.Seconds(), + ToColdStorageDuration: int64(toColdParsed.Seconds()), }, nil } @@ -944,14 +587,84 @@ func parseGetTTL(r *http.Request) (*model.GetTTLParams, error) { return &model.GetTTLParams{Type: typeTTL, GetAllTTL: getAllTTL}, nil } -func parseUserPreferences(r *http.Request) (*model.UserPreferences, error) { +func parseUserRequest(r *http.Request) (*model.User, error) { + var req model.User + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, err + } + return &req, nil +} - var userPreferences model.UserPreferences - err := json.NewDecoder(r.Body).Decode(&userPreferences) - if err != nil { +func parseInviteRequest(r *http.Request) (*model.InviteRequest, error) { + var req model.InviteRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, err + } + return &req, nil +} + +func parseRegisterRequest(r *http.Request) (*auth.RegisterRequest, error) { + var req auth.RegisterRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return nil, err } - return &userPreferences, nil + if err := auth.ValidatePassword(req.Password); err != nil { + return nil, err + } + return &req, nil +} + +func parseLoginRequest(r *http.Request) (*model.LoginRequest, error) { + var req model.LoginRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, err + } + + return &req, nil +} + +func parseUserRoleRequest(r *http.Request) (*model.UserRole, error) { + var req model.UserRole + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, err + } + + return &req, nil +} + +func parseEditOrgRequest(r *http.Request) (*model.Organization, error) { + var req model.Organization + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, err + } + + return &req, nil +} + +func parseResetPasswordRequest(r *http.Request) (*model.ResetPasswordRequest, error) { + var req model.ResetPasswordRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, err + } + if err := auth.ValidatePassword(req.Password); err != nil { + return nil, err + } + + return &req, nil +} + +func parseChangePasswordRequest(r *http.Request) (*model.ChangePasswordRequest, error) { + id := mux.Vars(r)["id"] + var req model.ChangePasswordRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, err + } + req.UserId = id + if err := auth.ValidatePassword(req.NewPassword); err != nil { + return nil, err + } + + return &req, nil } diff --git a/pkg/query-service/app/parser/metrics.go b/pkg/query-service/app/parser/metrics.go new file mode 100644 index 0000000000..279331ba3c --- /dev/null +++ b/pkg/query-service/app/parser/metrics.go @@ -0,0 +1,56 @@ +package parser + +import ( + "encoding/json" + "fmt" + "net/http" + + "go.signoz.io/query-service/model" +) + +func ParseMetricQueryRangeParams(r *http.Request) (*model.QueryRangeParamsV2, *model.ApiError) { + + var postData *model.QueryRangeParamsV2 + err := json.NewDecoder(r.Body).Decode(&postData) + + if err != nil { + return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err} + } + + return nil, nil +} + +func ParseMetricAutocompleteTagParams(r *http.Request) (*model.MetricAutocompleteTagParams, *model.ApiError) { + + metricName := r.URL.Query().Get("metricName") + if len(metricName) == 0 { + err := fmt.Errorf("metricName not present in params") + return nil, &model.ApiError{Typ: model.ErrorBadData, Err: err} + } + + tagsStr := r.URL.Query().Get("tags") + // fmt.Println(tagsStr) + + // parsing tags + var tags map[string]string + if tagsStr != "" && len(tagsStr) != 0 { + + err := json.Unmarshal([]byte(tagsStr), &tags) + if err != nil { + return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("unable to parse tags in params: %v", err)} + } + } + + matchText := r.URL.Query().Get("match") + + tagKey := r.URL.Query().Get("tagKey") + + metricAutocompleteTagParams := &model.MetricAutocompleteTagParams{ + MetricName: metricName, + MetricTags: tags, + Match: matchText, + TagKey: tagKey, + } + + return metricAutocompleteTagParams, nil +} diff --git a/pkg/query-service/app/server.go b/pkg/query-service/app/server.go index 62e44fc522..7908e79c18 100644 --- a/pkg/query-service/app/server.go +++ b/pkg/query-service/app/server.go @@ -1,6 +1,7 @@ package app import ( + "context" "fmt" "net" "net/http" @@ -14,7 +15,6 @@ import ( "github.com/soheilhy/cmux" "go.signoz.io/query-service/app/clickhouseReader" "go.signoz.io/query-service/app/dashboards" - "go.signoz.io/query-service/app/druidReader" "go.signoz.io/query-service/constants" "go.signoz.io/query-service/dao" "go.signoz.io/query-service/healthcheck" @@ -69,6 +69,10 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) { // return nil, err // } + if err := dao.InitDao("sqlite", constants.RELATIONAL_DATASOURCE_PATH); err != nil { + return nil, err + } + s := &Server{ // logger: logger, // querySvc: querySvc, @@ -103,7 +107,7 @@ func (s *Server) createHTTPServer() (*http.Server, error) { storage := os.Getenv("STORAGE") if storage == "druid" { zap.S().Info("Using Apache Druid as datastore ...") - reader = druidReader.NewReader(localDB) + // reader = druidReader.NewReader(localDB) } else if storage == "clickhouse" { zap.S().Info("Using ClickHouse as datastore ...") clickhouseReader := clickhouseReader.NewReader(localDB) @@ -113,27 +117,25 @@ func (s *Server) createHTTPServer() (*http.Server, error) { return nil, fmt.Errorf("Storage type: %s is not supported in query service", storage) } - relationalDB, err := dao.FactoryDao("sqlite") - if err != nil { - return nil, err - } - - apiHandler, err := NewAPIHandler(&reader, relationalDB) + apiHandler, err := NewAPIHandler(&reader, dao.DB()) if err != nil { return nil, err } r := NewRouter() + r.Use(setTimeoutMiddleware) r.Use(s.analyticsMiddleware) r.Use(loggingMiddleware) apiHandler.RegisterRoutes(r) + apiHandler.RegisterMetricsRoutes(r) c := cors.New(cors.Options{ AllowedOrigins: []string{"*"}, // AllowCredentials: true, AllowedMethods: []string{"GET", "DELETE", "POST", "PUT"}, + AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"}, }) handler := c.Handler(r) @@ -171,6 +173,16 @@ func (s *Server) analyticsMiddleware(next http.Handler) http.Handler { }) } +func setTimeoutMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx, cancel := context.WithTimeout(r.Context(), constants.ContextTimeout*time.Second) + defer cancel() + + r = r.WithContext(ctx) + next.ServeHTTP(w, r) + }) +} + // initListener initialises listeners of the server func (s *Server) initListener() (cmux.CMux, error) { if s.separatePorts { // use separate ports and listeners each for gRPC and HTTP requests diff --git a/pkg/query-service/auth/auth.go b/pkg/query-service/auth/auth.go new file mode 100644 index 0000000000..1a5775334a --- /dev/null +++ b/pkg/query-service/auth/auth.go @@ -0,0 +1,377 @@ +package auth + +import ( + "context" + "fmt" + "time" + + "github.com/golang-jwt/jwt" + "github.com/google/uuid" + "github.com/pkg/errors" + "go.signoz.io/query-service/constants" + "go.signoz.io/query-service/dao" + "go.signoz.io/query-service/model" + "go.uber.org/zap" + "golang.org/x/crypto/bcrypt" +) + +const ( + opaqueTokenSize = 16 + minimumPasswordLength = 8 +) + +var ( + ErrorInvalidCreds = fmt.Errorf("Invalid credentials") +) + +// The root user should be able to invite people to create account on SigNoz cluster. +func Invite(ctx context.Context, req *model.InviteRequest) (*model.InviteResponse, error) { + zap.S().Debugf("Got an invite request for email: %s\n", req.Email) + + token, err := randomHex(opaqueTokenSize) + if err != nil { + return nil, errors.Wrap(err, "failed to generate invite token") + } + + user, apiErr := dao.DB().GetUserByEmail(ctx, req.Email) + if apiErr != nil { + return nil, errors.Wrap(apiErr.Err, "Failed to check already existing user") + } + + if user != nil { + return nil, errors.New("User already exists with the same email") + } + + if err := validateInviteRequest(req); err != nil { + return nil, errors.Wrap(err, "invalid invite request") + } + + jwtAdmin, err := ExtractJwtFromContext(ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to extract admin jwt token") + } + + adminUser, err := validateUser(jwtAdmin) + if err != nil { + return nil, errors.Wrap(err, "failed to validate admin jwt token") + } + + au, apiErr := dao.DB().GetUser(ctx, adminUser.Id) + if apiErr != nil { + return nil, errors.Wrap(err, "failed to query admin user from the DB") + } + inv := &model.InvitationObject{ + Name: req.Name, + Email: req.Email, + Token: token, + CreatedAt: time.Now().Unix(), + Role: req.Role, + OrgId: au.OrgId, + } + + if err := dao.DB().CreateInviteEntry(ctx, inv); err != nil { + return nil, errors.Wrap(err.Err, "failed to write to DB") + } + + return &model.InviteResponse{Email: inv.Email, InviteToken: inv.Token}, nil +} + +// RevokeInvite is used to revoke the invitation for the given email. +func RevokeInvite(ctx context.Context, email string) error { + zap.S().Debugf("RevokeInvite method invoked for email: %s\n", email) + + if !isValidEmail(email) { + return ErrorInvalidInviteToken + } + + if err := dao.DB().DeleteInvitation(ctx, email); err != nil { + return errors.Wrap(err.Err, "failed to write to DB") + } + return nil +} + +// GetInvite returns an invitation object for the given token. +func GetInvite(ctx context.Context, token string) (*model.InvitationResponseObject, error) { + zap.S().Debugf("GetInvite method invoked for token: %s\n", token) + + inv, apiErr := dao.DB().GetInviteFromToken(ctx, token) + if apiErr != nil { + return nil, errors.Wrap(apiErr.Err, "failed to query the DB") + } + + if inv == nil { + return nil, errors.New("user is not invited") + } + + // TODO(Ahsan): This is not the best way to add org name in the invite response. We should + // either include org name in the invite table or do a join query. + org, apiErr := dao.DB().GetOrg(ctx, inv.OrgId) + if apiErr != nil { + return nil, errors.Wrap(apiErr.Err, "failed to query the DB") + } + return &model.InvitationResponseObject{ + Name: inv.Name, + Email: inv.Email, + Token: inv.Token, + CreatedAt: inv.CreatedAt, + Role: inv.Role, + Organization: org.Name, + }, nil +} + +func validateInvite(ctx context.Context, req *RegisterRequest) (*model.InvitationObject, error) { + invitation, err := dao.DB().GetInviteFromEmail(ctx, req.Email) + if err != nil { + return nil, errors.Wrap(err.Err, "Failed to read from DB") + } + + if invitation == nil { + return nil, ErrorAskAdmin + } + + if invitation.Token != req.InviteToken { + return nil, ErrorInvalidInviteToken + } + + return invitation, nil +} + +func CreateResetPasswordToken(ctx context.Context, userId string) (*model.ResetPasswordEntry, error) { + token, err := randomHex(opaqueTokenSize) + if err != nil { + return nil, errors.Wrap(err, "failed to generate reset password token") + } + + req := &model.ResetPasswordEntry{ + UserId: userId, + Token: token, + } + if apiErr := dao.DB().CreateResetPasswordEntry(ctx, req); err != nil { + return nil, errors.Wrap(apiErr.Err, "failed to write to DB") + } + return req, nil +} + +func ResetPassword(ctx context.Context, req *model.ResetPasswordRequest) error { + entry, apiErr := dao.DB().GetResetPasswordEntry(ctx, req.Token) + if apiErr != nil { + return errors.Wrap(apiErr.Err, "failed to query the DB") + } + + if entry == nil { + return errors.New("Invalid reset password request") + } + + hash, err := passwordHash(req.Password) + if err != nil { + return errors.Wrap(err, "Failed to generate password hash") + } + + if apiErr := dao.DB().UpdateUserPassword(ctx, hash, entry.UserId); apiErr != nil { + return apiErr.Err + } + + if apiErr := dao.DB().DeleteResetPasswordEntry(ctx, req.Token); apiErr != nil { + return errors.Wrap(apiErr.Err, "failed to delete reset token from DB") + } + + return nil +} + +func ChangePassword(ctx context.Context, req *model.ChangePasswordRequest) error { + + user, apiErr := dao.DB().GetUser(ctx, req.UserId) + if apiErr != nil { + return errors.Wrap(apiErr.Err, "failed to query user from the DB") + } + + if user == nil || !passwordMatch(user.Password, req.OldPassword) { + return ErrorInvalidCreds + } + + hash, err := passwordHash(req.NewPassword) + if err != nil { + return errors.Wrap(err, "Failed to generate password hash") + } + + if apiErr := dao.DB().UpdateUserPassword(ctx, hash, user.Id); apiErr != nil { + return apiErr.Err + } + + return nil +} + +type RegisterRequest struct { + Name string `json:"name"` + OrgName string `json:"orgName"` + Email string `json:"email"` + Password string `json:"password"` + InviteToken string `json:"token"` +} + +// Register registers a new user. For the first register request, it doesn't need an invite token +// and also the first registration is an enforced ADMIN registration. Every subsequent request will +// need an invite token to go through. +func Register(ctx context.Context, req *RegisterRequest) *model.ApiError { + + zap.S().Debugf("Got a register request for email: %v\n", req.Email) + + // TODO(Ahsan): We should optimize it, shouldn't make an extra DB call everytime to know if + // this is the first register request. + users, apiErr := dao.DB().GetUsers(ctx) + if apiErr != nil { + zap.S().Debugf("GetUser failed, err: %v\n", apiErr.Err) + return apiErr + } + + var groupName, orgId string + + // If there are no user, then this first user is granted Admin role. Also, an org is created + // based on the request. Any other user can't use any other org name, if they do then + // registration will fail because of foreign key violation while create user. + // TODO(Ahsan): We need to re-work this logic for the case of multi-tenant system. + if len(users) == 0 { + org, apiErr := dao.DB().CreateOrg(ctx, &model.Organization{Name: req.OrgName}) + if apiErr != nil { + zap.S().Debugf("CreateOrg failed, err: %v\n", apiErr.Err) + return apiErr + } + groupName = constants.AdminGroup + orgId = org.Id + } + + if len(users) > 0 { + inv, err := validateInvite(ctx, req) + if err != nil { + return &model.ApiError{Err: err, Typ: model.ErrorUnauthorized} + } + org, apiErr := dao.DB().GetOrgByName(ctx, req.OrgName) + if apiErr != nil { + zap.S().Debugf("GetOrgByName failed, err: %v\n", apiErr.Err) + return apiErr + } + + groupName = inv.Role + if org != nil { + orgId = org.Id + } + } + + group, apiErr := dao.DB().GetGroupByName(ctx, groupName) + if apiErr != nil { + zap.S().Debugf("GetGroupByName failed, err: %v\n", apiErr.Err) + return apiErr + } + + hash, err := passwordHash(req.Password) + if err != nil { + return &model.ApiError{Err: err, Typ: model.ErrorUnauthorized} + } + + user := &model.User{ + Id: uuid.NewString(), + Name: req.Name, + Email: req.Email, + Password: hash, + CreatedAt: time.Now().Unix(), + ProfilePirctureURL: "", // Currently unused + GroupId: group.Id, + OrgId: orgId, + } + + // TODO(Ahsan): Ideally create user and delete invitation should happen in a txn. + _, apiErr = dao.DB().CreateUser(ctx, user) + if apiErr != nil { + zap.S().Debugf("CreateUser failed, err: %v\n", apiErr.Err) + return apiErr + } + + return dao.DB().DeleteInvitation(ctx, user.Email) +} + +// Login method returns access and refresh tokens on successful login, else it errors out. +func Login(ctx context.Context, request *model.LoginRequest) (*model.LoginResponse, error) { + zap.S().Debugf("Login method called for user: %s\n", request.Email) + + user, err := authenticateLogin(ctx, request) + if err != nil { + zap.S().Debugf("Failed to authenticate login request, %v", err) + return nil, err + } + + accessJwtExpiry := time.Now().Add(JwtExpiry).Unix() + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "id": user.Id, + "gid": user.GroupId, + "email": user.Email, + "exp": accessJwtExpiry, + }) + + accessJwt, err := token.SignedString([]byte(JwtSecret)) + if err != nil { + return nil, errors.Errorf("failed to encode jwt: %v", err) + } + + refreshJwtExpiry := time.Now().Add(JwtRefresh).Unix() + token = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "id": user.Id, + "gid": user.GroupId, + "email": user.Email, + "exp": refreshJwtExpiry, + }) + + refreshJwt, err := token.SignedString([]byte(JwtSecret)) + if err != nil { + return nil, errors.Errorf("failed to encode jwt: %v", err) + } + + return &model.LoginResponse{ + AccessJwt: accessJwt, + AccessJwtExpiry: accessJwtExpiry, + RefreshJwt: refreshJwt, + RefreshJwtExpiry: refreshJwtExpiry, + UserId: user.Id, + }, nil +} + +// authenticateLogin is responsible for querying the DB and validating the credentials. +func authenticateLogin(ctx context.Context, req *model.LoginRequest) (*model.UserPayload, error) { + + // If refresh token is valid, then simply authorize the login request. + if len(req.RefreshToken) > 0 { + user, err := validateUser(req.RefreshToken) + if err != nil { + return nil, errors.Wrap(err, "failed to validate refresh token") + } + + return user, nil + } + + user, err := dao.DB().GetUserByEmail(ctx, req.Email) + if err != nil { + return nil, errors.Wrap(err.Err, "user not found") + } + if user == nil || !passwordMatch(user.Password, req.Password) { + return nil, ErrorInvalidCreds + } + return user, nil +} + +// Generate hash from the password. +func passwordHash(pass string) (string, error) { + hash, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost) + if err != nil { + return "", err + } + return string(hash), nil +} + +// Checks if the given password results in the given hash. +func passwordMatch(hash, password string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + if err != nil { + return false + } + return true +} diff --git a/pkg/query-service/auth/jwt.go b/pkg/query-service/auth/jwt.go new file mode 100644 index 0000000000..35ed509048 --- /dev/null +++ b/pkg/query-service/auth/jwt.go @@ -0,0 +1,94 @@ +package auth + +import ( + "context" + "net/http" + "time" + + jwtmiddleware "github.com/auth0/go-jwt-middleware" + "github.com/golang-jwt/jwt" + "github.com/pkg/errors" + "go.signoz.io/query-service/model" + "go.uber.org/zap" + "google.golang.org/grpc/metadata" +) + +var ( + JwtSecret string + JwtExpiry = 30 * time.Minute + JwtRefresh = 30 * 24 * time.Hour +) + +func ParseJWT(jwtStr string) (jwt.MapClaims, error) { + token, err := jwt.Parse(jwtStr, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, errors.Errorf("unknown signing algo: %v", token.Header["alg"]) + } + return []byte(JwtSecret), nil + }) + + if err != nil { + return nil, errors.Wrapf(err, "failed to parse jwt token") + } + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok || !token.Valid { + return nil, errors.Errorf("Not a valid jwt claim") + } + return claims, nil +} + +func validateUser(tok string) (*model.UserPayload, error) { + claims, err := ParseJWT(tok) + if err != nil { + return nil, err + } + now := time.Now().Unix() + if !claims.VerifyExpiresAt(now, true) { + return nil, model.ErrorTokenExpired + } + return &model.UserPayload{ + User: model.User{ + Id: claims["id"].(string), + GroupId: claims["gid"].(string), + Email: claims["email"].(string), + }, + }, nil +} + +// AttachJwtToContext attached the jwt token from the request header to the context. +func AttachJwtToContext(ctx context.Context, r *http.Request) context.Context { + token, err := ExtractJwtFromRequest(r) + if err != nil { + zap.S().Debugf("Error while getting token from header, %v", err) + return ctx + } + + if len(token) > 0 { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + md = metadata.New(nil) + } + + md.Append("accessJwt", token) + ctx = metadata.NewIncomingContext(ctx, md) + } + return ctx +} + +func ExtractJwtFromContext(ctx context.Context) (string, error) { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return "", errors.New("No JWT metadata token found") + } + accessJwt := md.Get("accessJwt") + if len(accessJwt) == 0 { + return "", errors.New("No JWT token found") + } + + return accessJwt[0], nil +} + +func ExtractJwtFromRequest(r *http.Request) (string, error) { + return jwtmiddleware.FromAuthHeader(r) +} diff --git a/pkg/query-service/auth/rbac.go b/pkg/query-service/auth/rbac.go new file mode 100644 index 0000000000..d45a06e5d5 --- /dev/null +++ b/pkg/query-service/auth/rbac.go @@ -0,0 +1,94 @@ +package auth + +import ( + "context" + "fmt" + "net/http" + "regexp" + + "github.com/pkg/errors" + "go.signoz.io/query-service/constants" + "go.signoz.io/query-service/dao" + "go.signoz.io/query-service/model" +) + +type Group struct { + GroupID string + GroupName string +} + +type AuthCache struct { + AdminGroupId string + EditorGroupId string + ViewerGroupId string +} + +var AuthCacheObj AuthCache + +// InitAuthCache reads the DB and initialize the auth cache. +func InitAuthCache(ctx context.Context) error { + + setGroupId := func(groupName string, dest *string) error { + group, err := dao.DB().GetGroupByName(ctx, groupName) + if err != nil { + return errors.Wrapf(err.Err, "failed to get group %s", groupName) + } + *dest = group.Id + return nil + } + + if err := setGroupId(constants.AdminGroup, &AuthCacheObj.AdminGroupId); err != nil { + return err + } + if err := setGroupId(constants.EditorGroup, &AuthCacheObj.EditorGroupId); err != nil { + return err + } + if err := setGroupId(constants.ViewerGroup, &AuthCacheObj.ViewerGroupId); err != nil { + return err + } + + return nil +} + +func GetUserFromRequest(r *http.Request) (*model.UserPayload, error) { + accessJwt, err := ExtractJwtFromRequest(r) + if err != nil { + return nil, err + } + + user, err := validateUser(accessJwt) + if err != nil { + return nil, err + } + return user, nil +} + +func IsSelfAccessRequest(user *model.UserPayload, id string) bool { return user.Id == id } + +func IsViewer(user *model.UserPayload) bool { return user.GroupId == AuthCacheObj.ViewerGroupId } +func IsEditor(user *model.UserPayload) bool { return user.GroupId == AuthCacheObj.EditorGroupId } +func IsAdmin(user *model.UserPayload) bool { return user.GroupId == AuthCacheObj.AdminGroupId } + +func ValidatePassword(password string) error { + if len(password) < minimumPasswordLength { + return errors.Errorf("Password should be atleast %d characters.", minimumPasswordLength) + } + + num := `[0-9]{1}` + lower := `[a-z]{1}` + upper := `[A-Z]{1}` + symbol := `[!@#$&*]{1}` + if b, err := regexp.MatchString(num, password); !b || err != nil { + return fmt.Errorf("password should have atleast one number") + } + if b, err := regexp.MatchString(lower, password); !b || err != nil { + return fmt.Errorf("password should have atleast one lower case letter") + } + if b, err := regexp.MatchString(upper, password); !b || err != nil { + return fmt.Errorf("password should have atleast one upper case letter") + } + if b, err := regexp.MatchString(symbol, password); !b || err != nil { + return fmt.Errorf("password should have atleast one special character from !@#$&* ") + } + return nil +} diff --git a/pkg/query-service/auth/utils.go b/pkg/query-service/auth/utils.go new file mode 100644 index 0000000000..4de59ea2c0 --- /dev/null +++ b/pkg/query-service/auth/utils.go @@ -0,0 +1,56 @@ +package auth + +import ( + "crypto/rand" + "encoding/hex" + + "github.com/pkg/errors" + "go.signoz.io/query-service/constants" + "go.signoz.io/query-service/model" +) + +var ( + ErrorEmptyRequest = errors.New("Empty request") + ErrorInvalidEmail = errors.New("Invalid email") + ErrorInvalidRole = errors.New("Invalid role") + + ErrorInvalidInviteToken = errors.New("Invalid invite token") + ErrorAskAdmin = errors.New("You are not allowed to create an account. Please ask your admin to send an invite link") +) + +func randomHex(sz int) (string, error) { + bytes := make([]byte, sz) + if _, err := rand.Read(bytes); err != nil { + return "", err + } + return hex.EncodeToString(bytes), nil +} + +func isValidRole(role string) bool { + switch role { + case constants.AdminGroup, constants.EditorGroup, constants.ViewerGroup: + return true + default: + return false + } + return false +} + +func validateInviteRequest(req *model.InviteRequest) error { + if req == nil { + return ErrorEmptyRequest + } + if !isValidEmail(req.Email) { + return ErrorInvalidEmail + } + + if !isValidRole(req.Role) { + return ErrorInvalidRole + } + return nil +} + +// TODO(Ahsan): Implement check on email semantic. +func isValidEmail(email string) bool { + return true +} diff --git a/pkg/query-service/config/dashboards/.gitkeep b/pkg/query-service/config/dashboards/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/pkg/query-service/constants/auth.go b/pkg/query-service/constants/auth.go new file mode 100644 index 0000000000..7248f9dc1a --- /dev/null +++ b/pkg/query-service/constants/auth.go @@ -0,0 +1,7 @@ +package constants + +const ( + AdminGroup = "ADMIN" + EditorGroup = "EDITOR" + ViewerGroup = "VIEWER" +) diff --git a/pkg/query-service/constants/constants.go b/pkg/query-service/constants/constants.go index 6937d1709b..f4a00cec23 100644 --- a/pkg/query-service/constants/constants.go +++ b/pkg/query-service/constants/constants.go @@ -5,10 +5,10 @@ import ( "strconv" ) -const HTTPHostPort = "0.0.0.0:8080" +const ( + HTTPHostPort = "0.0.0.0:8080" +) -var DruidClientUrl = os.Getenv("DruidClientUrl") -var DruidDatasource = os.Getenv("DruidDatasource") var DEFAULT_TELEMETRY_ANONYMOUS = false func IsTelemetryEnabled() bool { @@ -30,7 +30,7 @@ func GetAlertManagerApiPrefix() string { return "http://alertmanager:9093/api/" } -// Alert manager channel subpath +// Alert manager channel subpath var AmChannelApiPath = GetOrDefaultEnv("ALERTMANAGER_API_CHANNEL_PATH", "v1/routes") var RELATIONAL_DATASOURCE_PATH = GetOrDefaultEnv("SIGNOZ_LOCAL_DB_PATH", "/var/lib/signoz/signoz.db") @@ -45,13 +45,23 @@ const ( Component = "component" OperationDB = "name" OperationRequest = "operation" + Status = "status" + Duration = "duration" + DBName = "dbName" + DBOperation = "dbOperation" + DBSystem = "dbSystem" + MsgSystem = "msgSystem" + MsgOperation = "msgOperation" + Timestamp = "timestamp" + Descending = "descending" + Ascending = "ascending" + ContextTimeout = 60 // seconds ) - func GetOrDefaultEnv(key string, fallback string) string { v := os.Getenv(key) if len(v) == 0 { return fallback } return v -} \ No newline at end of file +} diff --git a/pkg/query-service/constants/constants_test.go b/pkg/query-service/constants/constants_test.go index 97bed19271..59c35eae12 100644 --- a/pkg/query-service/constants/constants_test.go +++ b/pkg/query-service/constants/constants_test.go @@ -1,9 +1,10 @@ package constants import ( - . "github.com/smartystreets/goconvey/convey" "os" "testing" + + . "github.com/smartystreets/goconvey/convey" ) func TestGetAlertManagerApiPrefix(t *testing.T) { diff --git a/pkg/query-service/dao/factory.go b/pkg/query-service/dao/factory.go index 92f2b7e534..710520421b 100644 --- a/pkg/query-service/dao/factory.go +++ b/pkg/query-service/dao/factory.go @@ -3,24 +3,31 @@ package dao import ( "fmt" - "go.signoz.io/query-service/constants" - "go.signoz.io/query-service/dao/interfaces" + "github.com/pkg/errors" "go.signoz.io/query-service/dao/sqlite" ) -func FactoryDao(engine string) (*interfaces.ModelDao, error) { - var i interfaces.ModelDao +var db ModelDao + +func InitDao(engine, path string) error { var err error switch engine { case "sqlite": - i, err = sqlite.InitDB(constants.RELATIONAL_DATASOURCE_PATH) + db, err = sqlite.InitDB(path) if err != nil { - return nil, err + return errors.Wrap(err, "failed to initialize DB") } default: - return nil, fmt.Errorf("RelationalDB type: %s is not supported in query service", engine) + return fmt.Errorf("RelationalDB type: %s is not supported in query service", engine) } - - return &i, nil + return nil +} + +func DB() ModelDao { + if db == nil { + // Should never reach here + panic("GetDB called before initialization") + } + return db } diff --git a/pkg/query-service/dao/interface.go b/pkg/query-service/dao/interface.go new file mode 100644 index 0000000000..bf105ba30e --- /dev/null +++ b/pkg/query-service/dao/interface.go @@ -0,0 +1,56 @@ +package dao + +import ( + "context" + + "go.signoz.io/query-service/model" +) + +type ModelDao interface { + Queries + Mutations +} + +type Queries interface { + GetInviteFromEmail(ctx context.Context, email string) (*model.InvitationObject, *model.ApiError) + GetInviteFromToken(ctx context.Context, token string) (*model.InvitationObject, *model.ApiError) + GetInvites(ctx context.Context) ([]model.InvitationObject, *model.ApiError) + + GetUser(ctx context.Context, id string) (*model.UserPayload, *model.ApiError) + GetUserByEmail(ctx context.Context, email string) (*model.UserPayload, *model.ApiError) + GetUsers(ctx context.Context) ([]model.UserPayload, *model.ApiError) + + GetGroup(ctx context.Context, id string) (*model.Group, *model.ApiError) + GetGroupByName(ctx context.Context, name string) (*model.Group, *model.ApiError) + GetGroups(ctx context.Context) ([]model.Group, *model.ApiError) + + GetOrgs(ctx context.Context) ([]model.Organization, *model.ApiError) + GetOrgByName(ctx context.Context, name string) (*model.Organization, *model.ApiError) + GetOrg(ctx context.Context, id string) (*model.Organization, *model.ApiError) + + GetResetPasswordEntry(ctx context.Context, token string) (*model.ResetPasswordEntry, *model.ApiError) + GetUsersByOrg(ctx context.Context, orgId string) ([]model.UserPayload, *model.ApiError) + GetUsersByGroup(ctx context.Context, groupId string) ([]model.UserPayload, *model.ApiError) +} + +type Mutations interface { + CreateInviteEntry(ctx context.Context, req *model.InvitationObject) *model.ApiError + DeleteInvitation(ctx context.Context, email string) *model.ApiError + + CreateUser(ctx context.Context, user *model.User) (*model.User, *model.ApiError) + EditUser(ctx context.Context, update *model.User) (*model.User, *model.ApiError) + DeleteUser(ctx context.Context, id string) *model.ApiError + + CreateGroup(ctx context.Context, group *model.Group) (*model.Group, *model.ApiError) + DeleteGroup(ctx context.Context, id string) *model.ApiError + + CreateOrg(ctx context.Context, org *model.Organization) (*model.Organization, *model.ApiError) + EditOrg(ctx context.Context, org *model.Organization) *model.ApiError + DeleteOrg(ctx context.Context, id string) *model.ApiError + + CreateResetPasswordEntry(ctx context.Context, req *model.ResetPasswordEntry) *model.ApiError + DeleteResetPasswordEntry(ctx context.Context, token string) *model.ApiError + + UpdateUserPassword(ctx context.Context, hash, userId string) *model.ApiError + UpdateUserGroup(ctx context.Context, userId, groupId string) *model.ApiError +} diff --git a/pkg/query-service/dao/interfaces/interface.go b/pkg/query-service/dao/interfaces/interface.go deleted file mode 100644 index e7d043fbf7..0000000000 --- a/pkg/query-service/dao/interfaces/interface.go +++ /dev/null @@ -1,5 +0,0 @@ -package interfaces - -type ModelDao interface { - UserPreferenceDao -} diff --git a/pkg/query-service/dao/interfaces/userPreference.go b/pkg/query-service/dao/interfaces/userPreference.go deleted file mode 100644 index c4770ae2de..0000000000 --- a/pkg/query-service/dao/interfaces/userPreference.go +++ /dev/null @@ -1,12 +0,0 @@ -package interfaces - -import ( - "context" - - "go.signoz.io/query-service/model" -) - -type UserPreferenceDao interface { - UpdateUserPreferece(ctx context.Context, userPreferences *model.UserPreferences) *model.ApiError - FetchUserPreference(ctx context.Context) (*model.UserPreferences, *model.ApiError) -} diff --git a/pkg/query-service/dao/sqlite/connection.go b/pkg/query-service/dao/sqlite/connection.go index 8237cc21ee..b66ddeec12 100644 --- a/pkg/query-service/dao/sqlite/connection.go +++ b/pkg/query-service/dao/sqlite/connection.go @@ -5,8 +5,11 @@ import ( "fmt" "github.com/jmoiron/sqlx" + "github.com/pkg/errors" "go.signoz.io/query-service/constants" + "go.signoz.io/query-service/model" "go.signoz.io/query-service/telemetry" + "go.uber.org/zap" ) type ModelDaoSqlite struct { @@ -19,52 +22,137 @@ func InitDB(dataSourceName string) (*ModelDaoSqlite, error) { db, err := sqlx.Open("sqlite3", dataSourceName) if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to Open sqlite3 DB") } db.SetMaxOpenConns(10) - table_schema := `CREATE TABLE IF NOT EXISTS user_preferences ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - uuid TEXT NOT NULL, - isAnonymous INTEGER NOT NULL DEFAULT 0 CHECK(isAnonymous IN (0,1)), - hasOptedUpdates INTEGER NOT NULL DEFAULT 1 CHECK(hasOptedUpdates IN (0,1)) - );` + table_schema := ` + PRAGMA foreign_keys = ON; + + CREATE TABLE IF NOT EXISTS invites ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + email TEXT NOT NULL UNIQUE, + token TEXT NOT NULL, + created_at INTEGER NOT NULL, + role TEXT NOT NULL, + org_id TEXT NOT NULL, + FOREIGN KEY(org_id) REFERENCES organizations(id) + ); + CREATE TABLE IF NOT EXISTS organizations ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + created_at INTEGER NOT NULL, + is_anonymous INTEGER NOT NULL DEFAULT 0 CHECK(is_anonymous IN (0,1)), + has_opted_updates INTEGER NOT NULL DEFAULT 1 CHECK(has_opted_updates IN (0,1)) + ); + CREATE TABLE IF NOT EXISTS users ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + email TEXT NOT NULL UNIQUE, + password TEXT NOT NULL, + created_at INTEGER NOT NULL, + profile_picture_url TEXT, + group_id TEXT NOT NULL, + org_id TEXT NOT NULL, + FOREIGN KEY(group_id) REFERENCES groups(id), + FOREIGN KEY(org_id) REFERENCES organizations(id) + ); + CREATE TABLE IF NOT EXISTS groups ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL UNIQUE + ); + CREATE TABLE IF NOT EXISTS reset_password_request ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT NOT NULL, + token TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(id) + ); + ` _, err = db.Exec(table_schema) if err != nil { - return nil, fmt.Errorf("Error in creating user_preferences table: ", err.Error()) + return nil, fmt.Errorf("Error in creating tables: %v", err.Error()) } mds := &ModelDaoSqlite{db: db} - err = mds.initializeUserPreferences() - if err != nil { + ctx := context.Background() + if err := mds.initializeOrgPreferences(ctx); err != nil { + return nil, err + } + if err := mds.initializeRBAC(ctx); err != nil { return nil, err } - return mds, nil + return mds, nil } -func (mds *ModelDaoSqlite) initializeUserPreferences() error { + +// initializeOrgPreferences initializes in-memory telemetry settings. It is planned to have +// multiple orgs in the system. In case of multiple orgs, there will be separate instance +// of in-memory telemetry for each of the org, having their own settings. As of now, we only +// have one org so this method relies on the settings of this org to initialize the telemetry etc. +// TODO(Ahsan): Make it multi-tenant when we move to a system with multiple orgs. +func (mds *ModelDaoSqlite) initializeOrgPreferences(ctx context.Context) error { // set anonymous setting as default in case of any failures to fetch UserPreference in below section telemetry.GetInstance().SetTelemetryAnonymous(constants.DEFAULT_TELEMETRY_ANONYMOUS) - ctx := context.Background() - userPreference, apiError := mds.FetchUserPreference(ctx) + orgs, apiError := mds.GetOrgs(ctx) + if apiError != nil { + return apiError.Err + } - if apiError != nil { - return apiError.Err + if len(orgs) > 1 { + return errors.Errorf("Found %d organizations, expected one or none.", len(orgs)) } - if userPreference == nil { - userPreference, apiError = mds.CreateDefaultUserPreference(ctx) - } - if apiError != nil { - return apiError.Err + + var org model.Organization + if len(orgs) == 1 { + org = orgs[0] } // set telemetry fields from userPreferences - telemetry.GetInstance().SetTelemetryAnonymous(userPreference.GetIsAnonymous()) - telemetry.GetInstance().SetDistinctId(userPreference.GetUUID()) + telemetry.GetInstance().SetDistinctId(org.Id) return nil } + +// initializeRBAC creates the ADMIN, EDITOR and VIEWER groups if they are not present. +func (mds *ModelDaoSqlite) initializeRBAC(ctx context.Context) error { + f := func(groupName string) error { + _, err := mds.createGroupIfNotPresent(ctx, groupName) + return errors.Wrap(err, "Failed to create group") + } + + if err := f(constants.AdminGroup); err != nil { + return err + } + if err := f(constants.EditorGroup); err != nil { + return err + } + if err := f(constants.ViewerGroup); err != nil { + return err + } + + return nil +} + +func (mds *ModelDaoSqlite) createGroupIfNotPresent(ctx context.Context, + name string) (*model.Group, error) { + + group, err := mds.GetGroupByName(ctx, name) + if err != nil { + return nil, errors.Wrap(err.Err, "Failed to query for root group") + } + if group != nil { + return group, nil + } + + zap.S().Debugf("%s is not found, creating it", name) + group, cErr := mds.CreateGroup(ctx, &model.Group{Name: name}) + if cErr != nil { + return nil, cErr.Err + } + return group, nil +} diff --git a/pkg/query-service/dao/sqlite/rbac.go b/pkg/query-service/dao/sqlite/rbac.go new file mode 100644 index 0000000000..9c74f812c2 --- /dev/null +++ b/pkg/query-service/dao/sqlite/rbac.go @@ -0,0 +1,520 @@ +package sqlite + +import ( + "context" + "fmt" + "time" + + "github.com/google/uuid" + "github.com/pkg/errors" + "go.signoz.io/query-service/model" + "go.signoz.io/query-service/telemetry" +) + +func (mds *ModelDaoSqlite) CreateInviteEntry(ctx context.Context, + req *model.InvitationObject) *model.ApiError { + + _, err := mds.db.ExecContext(ctx, + `INSERT INTO invites (email, name, token, role, created_at, org_id) + VALUES (?, ?, ?, ?, ?, ?);`, + req.Email, req.Name, req.Token, req.Role, req.CreatedAt, req.OrgId) + if err != nil { + return &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + return nil +} + +func (mds *ModelDaoSqlite) DeleteInvitation(ctx context.Context, email string) *model.ApiError { + _, err := mds.db.ExecContext(ctx, `DELETE from invites where email=?;`, email) + if err != nil { + return &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + return nil +} + +func (mds *ModelDaoSqlite) GetInviteFromEmail(ctx context.Context, email string, +) (*model.InvitationObject, *model.ApiError) { + + invites := []model.InvitationObject{} + err := mds.db.Select(&invites, + `SELECT * FROM invites WHERE email=?;`, email) + + if err != nil { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + if len(invites) > 1 { + return nil, &model.ApiError{ + Typ: model.ErrorInternal, + Err: errors.Errorf("Found multiple invites for the email: %s", email)} + } + + if len(invites) == 0 { + return nil, nil + } + return &invites[0], nil +} + +func (mds *ModelDaoSqlite) GetInviteFromToken(ctx context.Context, token string, +) (*model.InvitationObject, *model.ApiError) { + + invites := []model.InvitationObject{} + err := mds.db.Select(&invites, + `SELECT * FROM invites WHERE token=?;`, token) + + if err != nil { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + if len(invites) > 1 { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + + if len(invites) == 0 { + return nil, nil + } + return &invites[0], nil +} + +func (mds *ModelDaoSqlite) GetInvites(ctx context.Context, +) ([]model.InvitationObject, *model.ApiError) { + + invites := []model.InvitationObject{} + err := mds.db.Select(&invites, "SELECT * FROM invites") + if err != nil { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + return invites, nil +} + +func (mds *ModelDaoSqlite) CreateOrg(ctx context.Context, + org *model.Organization) (*model.Organization, *model.ApiError) { + + org.Id = uuid.NewString() + org.CreatedAt = time.Now().Unix() + _, err := mds.db.ExecContext(ctx, + `INSERT INTO organizations (id, name, created_at) VALUES (?, ?, ?);`, + org.Id, org.Name, org.CreatedAt) + + if err != nil { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + return org, nil +} + +func (mds *ModelDaoSqlite) GetOrg(ctx context.Context, + id string) (*model.Organization, *model.ApiError) { + + orgs := []model.Organization{} + err := mds.db.Select(&orgs, `SELECT * FROM organizations WHERE id=?;`, id) + + if err != nil { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + if len(orgs) > 1 { + return nil, &model.ApiError{ + Typ: model.ErrorInternal, + Err: errors.New("Found multiple org with same ID"), + } + } + + if len(orgs) == 0 { + return nil, nil + } + return &orgs[0], nil +} + +func (mds *ModelDaoSqlite) GetOrgByName(ctx context.Context, + name string) (*model.Organization, *model.ApiError) { + + orgs := []model.Organization{} + + if err := mds.db.Select(&orgs, `SELECT * FROM organizations WHERE name=?;`, name); err != nil { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + + if len(orgs) > 1 { + return nil, &model.ApiError{ + Typ: model.ErrorInternal, + Err: errors.New("Multiple orgs with same ID found"), + } + } + if len(orgs) == 0 { + return nil, nil + } + return &orgs[0], nil +} + +func (mds *ModelDaoSqlite) GetOrgs(ctx context.Context) ([]model.Organization, *model.ApiError) { + orgs := []model.Organization{} + err := mds.db.Select(&orgs, `SELECT * FROM organizations`) + + if err != nil { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + return orgs, nil +} + +func (mds *ModelDaoSqlite) EditOrg(ctx context.Context, org *model.Organization) *model.ApiError { + + q := `UPDATE organizations SET name=?,has_opted_updates=?,is_anonymous=? WHERE id=?;` + + _, err := mds.db.ExecContext(ctx, q, org.Name, org.HasOptedUpdates, org.IsAnonymous, org.Id) + if err != nil { + return &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + + telemetry.GetInstance().SetTelemetryAnonymous(org.IsAnonymous) + return nil +} + +func (mds *ModelDaoSqlite) DeleteOrg(ctx context.Context, id string) *model.ApiError { + + _, err := mds.db.ExecContext(ctx, `DELETE from organizations where id=?;`, id) + if err != nil { + return &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + return nil +} + +func (mds *ModelDaoSqlite) CreateUser(ctx context.Context, + user *model.User) (*model.User, *model.ApiError) { + + _, err := mds.db.ExecContext(ctx, + `INSERT INTO users (id, name, email, password, created_at, profile_picture_url, group_id, org_id) + VALUES (?, ?, ?, ?, ?, ?, ?,?);`, + user.Id, user.Name, user.Email, user.Password, user.CreatedAt, + user.ProfilePirctureURL, user.GroupId, user.OrgId, + ) + + if err != nil { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + + data := map[string]interface{}{ + "name": user.Name, + "email": user.Email, + } + telemetry.GetInstance().IdentifyUser(user) + telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_EVENT_USER, data) + + return user, nil +} + +func (mds *ModelDaoSqlite) EditUser(ctx context.Context, + update *model.User) (*model.User, *model.ApiError) { + + _, err := mds.db.ExecContext(ctx, + `UPDATE users SET name=?,org_id=?,email=? WHERE id=?;`, update.Name, + update.OrgId, update.Email, update.Id) + if err != nil { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + return update, nil +} + +func (mds *ModelDaoSqlite) UpdateUserPassword(ctx context.Context, passwordHash, + userId string) *model.ApiError { + + q := `UPDATE users SET password=? WHERE id=?;` + if _, err := mds.db.ExecContext(ctx, q, passwordHash, userId); err != nil { + return &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + return nil +} + +func (mds *ModelDaoSqlite) UpdateUserGroup(ctx context.Context, userId, groupId string) *model.ApiError { + + q := `UPDATE users SET group_id=? WHERE id=?;` + if _, err := mds.db.ExecContext(ctx, q, groupId, userId); err != nil { + return &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + return nil +} + +func (mds *ModelDaoSqlite) DeleteUser(ctx context.Context, id string) *model.ApiError { + + result, err := mds.db.ExecContext(ctx, `DELETE from users where id=?;`, id) + if err != nil { + return &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + + affectedRows, err := result.RowsAffected() + if err != nil { + return &model.ApiError{Typ: model.ErrorExec, Err: err} + } + if affectedRows == 0 { + return &model.ApiError{ + Typ: model.ErrorNotFound, + Err: fmt.Errorf("no user found with id: %s", id), + } + } + + return nil +} + +func (mds *ModelDaoSqlite) GetUser(ctx context.Context, + id string) (*model.UserPayload, *model.ApiError) { + + users := []model.UserPayload{} + query := `select + u.id, + u.name, + u.email, + u.password, + u.created_at, + u.profile_picture_url, + u.org_id, + u.group_id, + g.name as role, + o.name as organization + from users u, groups g, organizations o + where + g.id=u.group_id and + o.id = u.org_id and + u.id=?;` + + if err := mds.db.Select(&users, query, id); err != nil { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + if len(users) > 1 { + return nil, &model.ApiError{ + Typ: model.ErrorInternal, + Err: errors.New("Found multiple users with same ID"), + } + } + + if len(users) == 0 { + return nil, nil + } + return &users[0], nil +} + +func (mds *ModelDaoSqlite) GetUserByEmail(ctx context.Context, + email string) (*model.UserPayload, *model.ApiError) { + + users := []model.UserPayload{} + query := `select + u.id, + u.name, + u.email, + u.password, + u.created_at, + u.profile_picture_url, + u.org_id, + u.group_id, + g.name as role, + o.name as organization + from users u, groups g, organizations o + where + g.id=u.group_id and + o.id = u.org_id and + u.email=?;` + + if err := mds.db.Select(&users, query, email); err != nil { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + if len(users) > 1 { + return nil, &model.ApiError{ + Typ: model.ErrorInternal, + Err: errors.New("Found multiple users with same ID."), + } + } + + if len(users) == 0 { + return nil, nil + } + return &users[0], nil +} + +func (mds *ModelDaoSqlite) GetUsers(ctx context.Context) ([]model.UserPayload, *model.ApiError) { + users := []model.UserPayload{} + + query := `select + u.id, + u.name, + u.email, + u.password, + u.created_at, + u.profile_picture_url, + u.org_id, + u.group_id, + g.name as role, + o.name as organization + from users u, groups g, organizations o + where + g.id = u.group_id and + o.id = u.org_id` + + err := mds.db.Select(&users, query) + + if err != nil { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + return users, nil +} + +func (mds *ModelDaoSqlite) GetUsersByOrg(ctx context.Context, + orgId string) ([]model.UserPayload, *model.ApiError) { + + users := []model.UserPayload{} + query := `select + u.id, + u.name, + u.email, + u.password, + u.created_at, + u.profile_picture_url, + u.org_id, + u.group_id, + g.name as role, + o.name as organization + from users u, groups g, organizations o + where + u.group_id = g.id and + u.org_id = o.id and + u.org_id=?;` + + if err := mds.db.Select(&users, query, orgId); err != nil { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + return users, nil +} + +func (mds *ModelDaoSqlite) GetUsersByGroup(ctx context.Context, + groupId string) ([]model.UserPayload, *model.ApiError) { + + users := []model.UserPayload{} + query := `select + u.id, + u.name, + u.email, + u.password, + u.created_at, + u.profile_picture_url, + u.org_id, + u.group_id, + g.name as role, + o.name as organization + from users u, groups g, organizations o + where + u.group_id = g.id and + o.id = u.org_id and + u.group_id=?;` + + if err := mds.db.Select(&users, query, groupId); err != nil { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + return users, nil +} + +func (mds *ModelDaoSqlite) CreateGroup(ctx context.Context, + group *model.Group) (*model.Group, *model.ApiError) { + + group.Id = uuid.NewString() + + q := `INSERT INTO groups (id, name) VALUES (?, ?);` + if _, err := mds.db.ExecContext(ctx, q, group.Id, group.Name); err != nil { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + + return group, nil +} + +func (mds *ModelDaoSqlite) DeleteGroup(ctx context.Context, id string) *model.ApiError { + + if _, err := mds.db.ExecContext(ctx, `DELETE from groups where id=?;`, id); err != nil { + return &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + return nil +} + +func (mds *ModelDaoSqlite) GetGroup(ctx context.Context, + id string) (*model.Group, *model.ApiError) { + + groups := []model.Group{} + if err := mds.db.Select(&groups, `SELECT id, name FROM groups WHERE id=?`, id); err != nil { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + + if len(groups) > 1 { + return nil, &model.ApiError{ + Typ: model.ErrorInternal, + Err: errors.New("Found multiple groups with same ID."), + } + } + + if len(groups) == 0 { + return nil, nil + } + return &groups[0], nil +} + +func (mds *ModelDaoSqlite) GetGroupByName(ctx context.Context, + name string) (*model.Group, *model.ApiError) { + + groups := []model.Group{} + if err := mds.db.Select(&groups, `SELECT id, name FROM groups WHERE name=?`, name); err != nil { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + + if len(groups) > 1 { + return nil, &model.ApiError{ + Typ: model.ErrorInternal, + Err: errors.New("Found multiple groups with same name"), + } + } + + if len(groups) == 0 { + return nil, nil + } + + return &groups[0], nil +} + +func (mds *ModelDaoSqlite) GetGroups(ctx context.Context) ([]model.Group, *model.ApiError) { + + groups := []model.Group{} + if err := mds.db.Select(&groups, "SELECT * FROM groups"); err != nil { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + + return groups, nil +} + +func (mds *ModelDaoSqlite) CreateResetPasswordEntry(ctx context.Context, + req *model.ResetPasswordEntry) *model.ApiError { + + q := `INSERT INTO reset_password_request (user_id, token) VALUES (?, ?);` + if _, err := mds.db.ExecContext(ctx, q, req.UserId, req.Token); err != nil { + return &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + return nil +} + +func (mds *ModelDaoSqlite) DeleteResetPasswordEntry(ctx context.Context, + token string) *model.ApiError { + _, err := mds.db.ExecContext(ctx, `DELETE from reset_password_request where token=?;`, token) + if err != nil { + return &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + return nil +} + +func (mds *ModelDaoSqlite) GetResetPasswordEntry(ctx context.Context, + token string) (*model.ResetPasswordEntry, *model.ApiError) { + + entries := []model.ResetPasswordEntry{} + + q := `SELECT user_id,token FROM reset_password_request WHERE token=?;` + if err := mds.db.Select(&entries, q, token); err != nil { + return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + if len(entries) > 1 { + return nil, &model.ApiError{Typ: model.ErrorInternal, + Err: errors.New("Multiple entries for reset token is found")} + } + + if len(entries) == 0 { + return nil, nil + } + return &entries[0], nil +} diff --git a/pkg/query-service/dao/sqlite/userPreferenceImpl.go b/pkg/query-service/dao/sqlite/userPreferenceImpl.go deleted file mode 100644 index 618a78ef1d..0000000000 --- a/pkg/query-service/dao/sqlite/userPreferenceImpl.go +++ /dev/null @@ -1,91 +0,0 @@ -package sqlite - -import ( - "context" - "fmt" - - "github.com/google/uuid" - "go.signoz.io/query-service/model" - "go.signoz.io/query-service/telemetry" - "go.uber.org/zap" -) - -func (mds *ModelDaoSqlite) FetchUserPreference(ctx context.Context) (*model.UserPreferences, *model.ApiError) { - - userPreferences := []model.UserPreferences{} - query := fmt.Sprintf("SELECT id, uuid, isAnonymous, hasOptedUpdates FROM user_preferences;") - - err := mds.db.Select(&userPreferences, query) - - if err != nil { - zap.S().Debug("Error in processing sql query: ", err) - return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} - } - - // zap.S().Info(query) - if len(userPreferences) > 1 { - zap.S().Debug("Error in processing sql query: ", fmt.Errorf("more than 1 row in user_preferences found")) - return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} - } - - if len(userPreferences) == 0 { - return nil, nil - } - - return &userPreferences[0], nil - -} - -func (mds *ModelDaoSqlite) UpdateUserPreferece(ctx context.Context, userPreferences *model.UserPreferences) *model.ApiError { - - tx, err := mds.db.Begin() - if err != nil { - return &model.ApiError{Typ: model.ErrorInternal, Err: err} - } - - userPreferencesFound, apiError := mds.FetchUserPreference(ctx) - if apiError != nil { - return apiError - } - - stmt, err := tx.Prepare(`UPDATE user_preferences SET isAnonymous=$1, hasOptedUpdates=$2 WHERE id=$3;`) - defer stmt.Close() - - if err != nil { - zap.S().Errorf("Error in preparing statement for INSERT to user_preferences\n", err) - tx.Rollback() - return &model.ApiError{Typ: model.ErrorInternal, Err: err} - } - - query_result, err := stmt.Exec(userPreferences.GetIsAnonymous(), userPreferences.GetHasOptedUpdate(), userPreferencesFound.GetId()) - if err != nil { - zap.S().Errorf("Error in Executing prepared statement for INSERT to user_preferences\n", err) - tx.Rollback() // return an error too, we may want to wrap them - return &model.ApiError{Typ: model.ErrorInternal, Err: err} - } - zap.S().Debug(query_result.RowsAffected()) - zap.S().Debug(userPreferences.GetIsAnonymous(), userPreferences.GetHasOptedUpdate(), userPreferencesFound.GetId()) - - err = tx.Commit() - if err != nil { - zap.S().Errorf("Error in commiting transaction for INSERT to user_preferences\n", err) - return &model.ApiError{Typ: model.ErrorInternal, Err: err} - } - telemetry.GetInstance().SetTelemetryAnonymous(userPreferences.GetIsAnonymous()) - - return nil -} - -func (mds *ModelDaoSqlite) CreateDefaultUserPreference(ctx context.Context) (*model.UserPreferences, *model.ApiError) { - - uuid := uuid.New().String() - _, err := mds.db.ExecContext(ctx, `INSERT INTO user_preferences (uuid, isAnonymous, hasOptedUpdates) VALUES (?, 0, 1);`, uuid) - - if err != nil { - zap.S().Errorf("Error in preparing statement for INSERT to user_preferences\n", err) - return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err} - } - - return mds.FetchUserPreference(ctx) - -} diff --git a/pkg/query-service/druidQuery/mysql-client.go b/pkg/query-service/druidQuery/mysql-client.go deleted file mode 100644 index 47776d8551..0000000000 --- a/pkg/query-service/druidQuery/mysql-client.go +++ /dev/null @@ -1,100 +0,0 @@ -package druidQuery - -import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "time" - - "go.uber.org/zap" -) - -const ( - SQL_ENDPOINT = "/druid/v2/sql" -) - -type SqlClient struct { - Url string - EndPoint string - Timeout time.Duration - - Debug bool - LastRequest string - LastResponse string -} - -type SqlQuery struct { - Query string `json:"query"` - Header bool `json:"header"` - ResultFormat string `json:"resultFormat"` -} - -func (c *SqlClient) Query(sql string, resultFormat string) ([]byte, error) { - - query := SqlQuery{ - Query: sql, - Header: true, - ResultFormat: resultFormat, - } - - reqJson, err := json.Marshal(query) - - // log request made to druid - zap.S().Info(string(reqJson)) - - result, err := c.QueryRaw(reqJson) - if err != nil { - return []byte("Error"), err - } - - return result, nil -} - -func (c *SqlClient) QueryRaw(req []byte) (result []byte, err error) { - if c.EndPoint == "" { - c.EndPoint = SQL_ENDPOINT - } - endPoint := c.EndPoint - if c.Debug { - endPoint += "?pretty" - c.LastRequest = string(req) - } - if err != nil { - return - } - - // By default, use 60 second timeout unless specified otherwise - // by the caller - clientTimeout := 60 * time.Second - if c.Timeout != 0 { - clientTimeout = c.Timeout - } - - httpClient := &http.Client{ - Timeout: clientTimeout, - } - - resp, err := httpClient.Post(c.Url+endPoint, "application/json", bytes.NewBuffer(req)) - if err != nil { - return - } - defer func() { - resp.Body.Close() - }() - - result, err = ioutil.ReadAll(resp.Body) - if err != nil { - return - } - if c.Debug { - c.LastResponse = string(result) - } - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("%s: %s", resp.Status, string(result)) - } - - return -} diff --git a/pkg/query-service/druidQuery/mysql-query.go b/pkg/query-service/druidQuery/mysql-query.go deleted file mode 100644 index a7ae41c727..0000000000 --- a/pkg/query-service/druidQuery/mysql-query.go +++ /dev/null @@ -1,571 +0,0 @@ -package druidQuery - -import ( - "encoding/json" - "fmt" - "strconv" - "time" - - "go.signoz.io/query-service/constants" - "go.signoz.io/query-service/model" - "go.uber.org/zap" -) - -func GetOperations(client *SqlClient, serviceName string) (*[]string, error) { - - sqlQuery := fmt.Sprintf(`SELECT DISTINCT(Name) FROM %s WHERE ServiceName='%s' AND __time > CURRENT_TIMESTAMP - INTERVAL '1' DAY`, constants.DruidDatasource, serviceName) - // zap.S().Debug(sqlQuery) - - response, err := client.Query(sqlQuery, "array") - - if err != nil { - zap.S().Error(sqlQuery, err) - return nil, fmt.Errorf("Something went wrong in druid query") - } - - // zap.S().Info(string(response)) - - res := new([][]string) - err = json.Unmarshal(response, res) - if err != nil { - zap.S().Error(err) - return nil, fmt.Errorf("Error in unmarshalling response from druid") - } - - var getOperationsReponse []string - for _, item := range *res { - getOperationsReponse = append(getOperationsReponse, item[0]) - } - getOperationsReponse = getOperationsReponse[1:] - return &getOperationsReponse, nil -} - -func GetServicesList(client *SqlClient) (*[]string, error) { - - sqlQuery := fmt.Sprintf(`SELECT DISTINCT(ServiceName) FROM %s WHERE __time > CURRENT_TIMESTAMP - INTERVAL '1' DAY`, constants.DruidDatasource) - // zap.S().Debug(sqlQuery) - - response, err := client.Query(sqlQuery, "array") - - if err != nil { - zap.S().Error(sqlQuery, err) - return nil, fmt.Errorf("Something went wrong in druid query") - } - - // zap.S().Info(string(response)) - - res := new([][]string) - err = json.Unmarshal(response, res) - if err != nil { - zap.S().Error(err) - return nil, fmt.Errorf("Error in unmarshalling response from druid") - } - - var servicesListReponse []string - for _, item := range *res { - servicesListReponse = append(servicesListReponse, item[0]) - } - servicesListReponse = servicesListReponse[1:] - return &servicesListReponse, nil -} - -func GetTags(client *SqlClient, serviceName string) (*[]model.TagItem, error) { - - var sqlQuery string - - if len(serviceName) != 0 { - sqlQuery = fmt.Sprintf(`SELECT TagsKeys as tagKeys, Count(TagsKeys) as "tagCount" FROM %s WHERE "ServiceName"='%s' AND "__time" > CURRENT_TIMESTAMP - INTERVAL '1' DAY GROUP BY TagsKeys ORDER BY tagCount DESC LIMIT 100`, constants.DruidDatasource, serviceName) - } else { - sqlQuery = fmt.Sprintf(`SELECT TagsKeys as tagKeys, Count(TagsKeys) as "tagCount" FROM %s WHERE "__time" > CURRENT_TIMESTAMP - INTERVAL '1' DAY GROUP BY TagsKeys ORDER BY tagCount DESC LIMIT 100`, constants.DruidDatasource) - } - - // zap.S().Debug(sqlQuery) - - response, err := client.Query(sqlQuery, "object") - - if err != nil { - zap.S().Error(sqlQuery, err) - return nil, fmt.Errorf("Something went wrong in druid query") - } - - // zap.S().Info(string(response)) - - res := new([]model.TagItem) - err = json.Unmarshal(response, res) - if err != nil { - zap.S().Error(err) - return nil, fmt.Errorf("Error in unmarshalling response from druid") - } - - tagResponse := (*res)[1:] - return &tagResponse, nil -} - -func GetTopEndpoints(client *SqlClient, query *model.GetTopEndpointsParams) (*[]model.TopEndpointsItem, error) { - - sqlQuery := fmt.Sprintf(`SELECT APPROX_QUANTILE_DS("QuantileDuration", 0.5) as p50, APPROX_QUANTILE_DS("QuantileDuration", 0.95) as p95, APPROX_QUANTILE_DS("QuantileDuration", 0.99) as p99, COUNT(SpanId) as numCalls, Name FROM "%s" WHERE "__time" >= '%s' AND "__time" <= '%s' AND "Kind"='2' and "ServiceName"='%s' GROUP BY Name`, constants.DruidDatasource, query.StartTime, query.EndTime, query.ServiceName) - - // zap.S().Debug(sqlQuery) - - response, err := client.Query(sqlQuery, "object") - - if err != nil { - zap.S().Error(query, err) - return nil, fmt.Errorf("Something went wrong in druid query") - } - - // zap.S().Info(string(response)) - - res := new([]model.TopEndpointsItem) - err = json.Unmarshal(response, res) - if err != nil { - zap.S().Error(err) - return nil, fmt.Errorf("Error in unmarshalling response from druid") - } - - topEnpointsResponse := (*res)[1:] - return &topEnpointsResponse, nil -} - -func GetUsage(client *SqlClient, query *model.GetUsageParams) (*[]model.UsageItem, error) { - - var sqlQuery string - - if len(query.ServiceName) != 0 { - - sqlQuery = fmt.Sprintf(`SELECT TIME_FLOOR(__time, '%s') as "time", COUNT(SpanId) as "count" FROM "%s" WHERE "__time" >= '%s' and "__time" <= '%s' and "ServiceName"='%s' GROUP BY TIME_FLOOR(__time, '%s')`, query.Period, constants.DruidDatasource, query.StartTime, query.EndTime, query.ServiceName, query.Period) - - } else { - sqlQuery = fmt.Sprintf(`SELECT TIME_FLOOR(__time, '%s') as "time", COUNT(SpanId) as "count" FROM "%s" WHERE "__time" >= '%s' and "__time" <= '%s' GROUP BY TIME_FLOOR(__time, '%s')`, query.Period, constants.DruidDatasource, query.StartTime, query.EndTime, query.Period) - } - - // zap.S().Debug(sqlQuery) - - response, err := client.Query(sqlQuery, "object") - - if err != nil { - zap.S().Error(query, err) - return nil, fmt.Errorf("Something went wrong in druid query") - } - - // zap.S().Info(string(response)) - - res := new([]model.UsageItem) - err = json.Unmarshal(response, res) - if err != nil { - zap.S().Error(err) - return nil, fmt.Errorf("Error in unmarshalling response from druid") - } - - for i, _ := range *res { - timeObj, _ := time.Parse(time.RFC3339Nano, (*res)[i].Time) - (*res)[i].Timestamp = int64(timeObj.UnixNano()) - (*res)[i].Time = "" - } - - usageResponse := (*res)[1:] - return &usageResponse, nil -} - -func GetServiceExternalAvgDuration(client *SqlClient, query *model.GetServiceOverviewParams) (*[]model.ServiceExternalItem, error) { - - sqlQuery := fmt.Sprintf(`SELECT TIME_FLOOR(__time, '%s') as "time", AVG(DurationNano) as "avgDuration" FROM %s WHERE ServiceName='%s' AND Kind='3' AND ExternalHttpUrl != '' AND "__time" >= '%s' AND "__time" <= '%s' - GROUP BY TIME_FLOOR(__time, '%s')`, query.Period, constants.DruidDatasource, query.ServiceName, query.StartTime, query.EndTime, query.Period) - - // zap.S().Debug(sqlQuery) - - response, err := client.Query(sqlQuery, "object") - - if err != nil { - zap.S().Error(query, err) - return nil, fmt.Errorf("Something went wrong in druid query") - } - - // responseStr := string(response) - // zap.S().Info(responseStr) - - res := new([]model.ServiceExternalItem) - err = json.Unmarshal(response, res) - if err != nil { - zap.S().Error(err) - return nil, fmt.Errorf("Error in unmarshalling response from druid") - } - - for i, _ := range *res { - timeObj, _ := time.Parse(time.RFC3339Nano, (*res)[i].Time) - (*res)[i].Timestamp = int64(timeObj.UnixNano()) - (*res)[i].Time = "" - (*res)[i].CallRate = float32((*res)[i].NumCalls) / float32(query.StepSeconds) - - } - - servicesExternalResponse := (*res)[1:] - return &servicesExternalResponse, nil -} - -func GetServiceExternalErrors(client *SqlClient, query *model.GetServiceOverviewParams) (*[]model.ServiceExternalItem, error) { - - sqlQuery := fmt.Sprintf(`SELECT TIME_FLOOR(__time, '%s') as "time", COUNT(SpanId) as "numCalls", ExternalHttpUrl as externalHttpUrl FROM %s WHERE ServiceName='%s' AND Kind='3' AND ExternalHttpUrl != '' AND (StatusCode >= 500 OR StatusCode=2) AND "__time" >= '%s' AND "__time" <= '%s' - GROUP BY TIME_FLOOR(__time, '%s'), ExternalHttpUrl`, query.Period, constants.DruidDatasource, query.ServiceName, query.StartTime, query.EndTime, query.Period) - - // zap.S().Debug(sqlQuery) - - response, err := client.Query(sqlQuery, "object") - - if err != nil { - zap.S().Error(query, err) - return nil, fmt.Errorf("Something went wrong in druid query") - } - - // responseStr := string(response) - // zap.S().Info(responseStr) - - res := new([]model.ServiceExternalItem) - err = json.Unmarshal(response, res) - if err != nil { - zap.S().Error(err) - return nil, fmt.Errorf("Error in unmarshalling response from druid") - } - - sqlQuery = fmt.Sprintf(`SELECT TIME_FLOOR(__time, '%s') as "time", COUNT(SpanId) as "numCalls", ExternalHttpUrl as externalHttpUrl FROM %s WHERE ServiceName='%s' AND Kind='3' AND ExternalHttpUrl != '' AND "__time" >= '%s' AND "__time" <= '%s' - GROUP BY TIME_FLOOR(__time, '%s'), ExternalHttpUrl`, query.Period, constants.DruidDatasource, query.ServiceName, query.StartTime, query.EndTime, query.Period) - - // zap.S().Debug(sqlQuery) - - responseTotal, err := client.Query(sqlQuery, "object") - - if err != nil { - zap.S().Error(query, err) - return nil, fmt.Errorf("Something went wrong in druid query") - } - - // responseStr := string(response) - // zap.S().Info(responseStr) - - resTotal := new([]model.ServiceExternalItem) - err = json.Unmarshal(responseTotal, resTotal) - if err != nil { - zap.S().Error(err) - return nil, fmt.Errorf("Error in unmarshalling response from druid") - } - - m := make(map[string]int) - - for j, _ := range *res { - timeObj, _ := time.Parse(time.RFC3339Nano, (*res)[j].Time) - m[strconv.FormatInt(timeObj.UnixNano(), 10)+"-"+(*res)[j].ExternalHttpUrl] = (*res)[j].NumCalls - } - - for i, _ := range *resTotal { - timeObj, _ := time.Parse(time.RFC3339Nano, (*resTotal)[i].Time) - (*resTotal)[i].Timestamp = int64(timeObj.UnixNano()) - (*resTotal)[i].Time = "" - (*resTotal)[i].CallRate = float32((*resTotal)[i].NumCalls) / float32(query.StepSeconds) - - if val, ok := m[strconv.FormatInt((*resTotal)[i].Timestamp, 10)+"-"+(*resTotal)[i].ExternalHttpUrl]; ok { - (*resTotal)[i].NumErrors = val - (*resTotal)[i].ErrorRate = float32((*resTotal)[i].NumErrors) * 100 / float32((*resTotal)[i].NumCalls) - } - (*resTotal)[i].CallRate = 0 - (*resTotal)[i].NumCalls = 0 - - } - - servicesExternalResponse := (*resTotal)[1:] - return &servicesExternalResponse, nil -} - -func GetServiceExternal(client *SqlClient, query *model.GetServiceOverviewParams) (*[]model.ServiceExternalItem, error) { - - sqlQuery := fmt.Sprintf(`SELECT TIME_FLOOR(__time, '%s') as "time", AVG(DurationNano) as "avgDuration", COUNT(SpanId) as "numCalls", ExternalHttpUrl as externalHttpUrl FROM %s WHERE ServiceName='%s' AND Kind='3' AND ExternalHttpUrl != '' - AND "__time" >= '%s' AND "__time" <= '%s' - GROUP BY TIME_FLOOR(__time, '%s'), ExternalHttpUrl`, query.Period, constants.DruidDatasource, query.ServiceName, query.StartTime, query.EndTime, query.Period) - - // zap.S().Debug(sqlQuery) - - response, err := client.Query(sqlQuery, "object") - - if err != nil { - zap.S().Error(query, err) - return nil, fmt.Errorf("Something went wrong in druid query") - } - - // responseStr := string(response) - // zap.S().Info(responseStr) - - res := new([]model.ServiceExternalItem) - err = json.Unmarshal(response, res) - if err != nil { - zap.S().Error(err) - return nil, fmt.Errorf("Error in unmarshalling response from druid") - } - - for i, _ := range *res { - timeObj, _ := time.Parse(time.RFC3339Nano, (*res)[i].Time) - (*res)[i].Timestamp = int64(timeObj.UnixNano()) - (*res)[i].Time = "" - (*res)[i].CallRate = float32((*res)[i].NumCalls) / float32(query.StepSeconds) - - } - - servicesExternalResponse := (*res)[1:] - return &servicesExternalResponse, nil -} - -func GetServiceDBOverview(client *SqlClient, query *model.GetServiceOverviewParams) (*[]model.ServiceDBOverviewItem, error) { - - sqlQuery := fmt.Sprintf(`SELECT TIME_FLOOR(__time, '%s') as "time", AVG(DurationNano) as "avgDuration", COUNT(SpanId) as "numCalls", DBSystem as "dbSystem" FROM %s WHERE ServiceName='%s' AND Kind='3' AND DBName IS NOT NULL - AND "__time" >= '%s' AND "__time" <= '%s' - GROUP BY TIME_FLOOR(__time, '%s'), DBSystem`, query.Period, constants.DruidDatasource, query.ServiceName, query.StartTime, query.EndTime, query.Period) - - // zap.S().Debug(sqlQuery) - - response, err := client.Query(sqlQuery, "object") - - if err != nil { - zap.S().Error(query, err) - return nil, fmt.Errorf("Something went wrong in druid query") - } - - // responseStr := string(response) - // zap.S().Info(responseStr) - - res := new([]model.ServiceDBOverviewItem) - err = json.Unmarshal(response, res) - if err != nil { - zap.S().Error(err) - return nil, fmt.Errorf("Error in unmarshalling response from druid") - } - - for i, _ := range *res { - timeObj, _ := time.Parse(time.RFC3339Nano, (*res)[i].Time) - (*res)[i].Timestamp = int64(timeObj.UnixNano()) - (*res)[i].Time = "" - (*res)[i].CallRate = float32((*res)[i].NumCalls) / float32(query.StepSeconds) - - } - - servicesDBOverviewResponse := (*res)[1:] - return &servicesDBOverviewResponse, nil -} - -func GetServiceOverview(client *SqlClient, query *model.GetServiceOverviewParams) (*[]model.ServiceOverviewItem, error) { - - sqlQuery := fmt.Sprintf(`SELECT TIME_FLOOR(__time, '%s') as "time", APPROX_QUANTILE_DS("QuantileDuration", 0.5) as p50, APPROX_QUANTILE_DS("QuantileDuration", 0.95) as p95, - APPROX_QUANTILE_DS("QuantileDuration", 0.99) as p99, COUNT("SpanId") as "numCalls" FROM "%s" WHERE "__time" >= '%s' and "__time" <= '%s' and "Kind"='2' and "ServiceName"='%s' GROUP BY TIME_FLOOR(__time, '%s') `, query.Period, constants.DruidDatasource, query.StartTime, query.EndTime, query.ServiceName, query.Period) - - // zap.S().Debug(sqlQuery) - - response, err := client.Query(sqlQuery, "object") - - if err != nil { - zap.S().Error(query, err) - return nil, fmt.Errorf("Something went wrong in druid query") - } - - // zap.S().Info(string(response)) - - res := new([]model.ServiceOverviewItem) - err = json.Unmarshal(response, res) - if err != nil { - zap.S().Error(err) - return nil, fmt.Errorf("Error in unmarshalling response from druid") - } - - sqlQuery = fmt.Sprintf(`SELECT TIME_FLOOR(__time, '%s') as "time", COUNT("SpanId") as "numErrors" FROM "%s" WHERE "__time" >= '%s' and "__time" <= '%s' and "Kind"='2' and "ServiceName"='%s' and ("StatusCode">=500 or "StatusCode"=2) GROUP BY TIME_FLOOR(__time, '%s') `, query.Period, constants.DruidDatasource, query.StartTime, query.EndTime, query.ServiceName, query.Period) - - // zap.S().Debug(sqlQuery) - - responseError, err := client.Query(sqlQuery, "object") - - if err != nil { - zap.S().Error(query, err) - return nil, fmt.Errorf("Something went wrong in druid query") - } - - // zap.S().Info(string(response)) - - resError := new([]model.ServiceErrorItem) - err = json.Unmarshal(responseError, resError) - if err != nil { - zap.S().Error(err) - return nil, fmt.Errorf("Error in unmarshalling response from druid") - } - - m := make(map[int64]int) - - for j, _ := range *resError { - timeObj, _ := time.Parse(time.RFC3339Nano, (*resError)[j].Time) - m[int64(timeObj.UnixNano())] = (*resError)[j].NumErrors - } - - for i, _ := range *res { - timeObj, _ := time.Parse(time.RFC3339Nano, (*res)[i].Time) - (*res)[i].Timestamp = int64(timeObj.UnixNano()) - (*res)[i].Time = "" - if val, ok := m[(*res)[i].Timestamp]; ok { - (*res)[i].NumErrors = val - } - (*res)[i].ErrorRate = float32((*res)[i].NumErrors) * 100 / float32((*res)[i].NumCalls) - (*res)[i].CallRate = float32((*res)[i].NumCalls) / float32(query.StepSeconds) - - } - - servicesOverviewResponse := (*res)[1:] - return &servicesOverviewResponse, nil -} - -func GetServices(client *SqlClient, query *model.GetServicesParams) (*[]model.ServiceItem, error) { - - sqlQuery := fmt.Sprintf(`SELECT APPROX_QUANTILE_DS("QuantileDuration", 0.99) as "p99", AVG("DurationNano") as "avgDuration", COUNT(SpanId) as numCalls, "ServiceName" as "serviceName" FROM %s WHERE "__time" >= '%s' and "__time" <= '%s' and "Kind"='2' GROUP BY "ServiceName" ORDER BY "p99" DESC`, constants.DruidDatasource, query.StartTime, query.EndTime) - - response, err := client.Query(sqlQuery, "object") - - // zap.S().Debug(sqlQuery) - - if err != nil { - zap.S().Error(query, err) - return nil, fmt.Errorf("Something went wrong in druid query") - } - - // zap.S().Info(string(response)) - - res := new([]model.ServiceItem) - err = json.Unmarshal(response, res) - if err != nil { - zap.S().Error(err) - return nil, fmt.Errorf("Error in unmarshalling response from druid") - } - - ////////////////// Below block gets 5xx of services - - sqlQuery = fmt.Sprintf(`SELECT COUNT(SpanId) as numErrors, "ServiceName" as "serviceName" FROM %s WHERE "__time" >= '%s' and "__time" <= '%s' and "Kind"='2' and ("StatusCode">=500 or "StatusCode"=2) GROUP BY "ServiceName"`, constants.DruidDatasource, query.StartTime, query.EndTime) - - responseError, err := client.Query(sqlQuery, "object") - - zap.S().Debug(sqlQuery) - - if err != nil { - zap.S().Error(query, err) - return nil, fmt.Errorf("Something went wrong in druid query") - } - - // zap.S().Info(string(response)) - - resError := new([]model.ServiceListErrorItem) - err = json.Unmarshal(responseError, resError) - if err != nil { - zap.S().Error(err) - return nil, fmt.Errorf("Error in unmarshalling response from druid") - } - - m := make(map[string]int) - - for j, _ := range *resError { - m[(*resError)[j].ServiceName] = (*resError)[j].NumErrors - } - - /////////////////////////////////////////// - - ////////////////// Below block gets 4xx of services - - sqlQuery = fmt.Sprintf(`SELECT COUNT(SpanId) as num4xx, "ServiceName" as "serviceName" FROM %s WHERE "__time" >= '%s' and "__time" <= '%s' and "Kind"='2' and "StatusCode">=400 and "StatusCode" < 500 GROUP BY "ServiceName"`, constants.DruidDatasource, query.StartTime, query.EndTime) - - response4xx, err := client.Query(sqlQuery, "object") - - // zap.S().Debug(sqlQuery) - - if err != nil { - zap.S().Error(query, err) - return nil, fmt.Errorf("Something went wrong in druid query") - } - - // zap.S().Info(string(response)) - - res4xx := new([]model.ServiceListErrorItem) - err = json.Unmarshal(response4xx, res4xx) - if err != nil { - zap.S().Error(err) - return nil, fmt.Errorf("Error in unmarshalling response from druid") - } - - m4xx := make(map[string]int) - - for j, _ := range *res4xx { - m4xx[(*res4xx)[j].ServiceName] = (*res4xx)[j].Num4xx - } - - /////////////////////////////////////////// - - for i, _ := range *res { - - if val, ok := m[(*res)[i].ServiceName]; ok { - (*res)[i].NumErrors = val - } - if val, ok := m4xx[(*res)[i].ServiceName]; ok { - (*res)[i].Num4XX = val - } - - (*res)[i].FourXXRate = float32((*res)[i].Num4XX) * 100 / float32((*res)[i].NumCalls) - (*res)[i].ErrorRate = float32((*res)[i].NumErrors) * 100 / float32((*res)[i].NumCalls) - (*res)[i].CallRate = float32((*res)[i].NumCalls) / float32(query.Period) - - } - servicesResponse := (*res)[1:] - return &servicesResponse, nil -} - -func GetServiceMapDependencies(client *SqlClient, query *model.GetServicesParams) (*[]model.ServiceMapDependencyResponseItem, error) { - - sqlQuery := fmt.Sprintf(`SELECT SpanId, ParentSpanId, ServiceName FROM %s WHERE "__time" >= '%s' AND "__time" <= '%s' ORDER BY __time DESC LIMIT 100000`, constants.DruidDatasource, query.StartTime, query.EndTime) - - // zap.S().Debug(sqlQuery) - - response, err := client.Query(sqlQuery, "object") - - if err != nil { - zap.S().Error(query, err) - return nil, fmt.Errorf("Something went wrong in druid query") - } - - // responseStr := string(response) - // zap.S().Info(responseStr) - - res := new([]model.ServiceMapDependencyItem) - err = json.Unmarshal(response, res) - if err != nil { - zap.S().Error(err) - return nil, fmt.Errorf("Error in unmarshalling response from druid") - } - // resCount := len(*res) - // fmt.Println(resCount) - - serviceMap := make(map[string]*model.ServiceMapDependencyResponseItem) - - spanId2ServiceNameMap := make(map[string]string) - for i, _ := range *res { - spanId2ServiceNameMap[(*res)[i].SpanId] = (*res)[i].ServiceName - } - for i, _ := range *res { - parent2childServiceName := spanId2ServiceNameMap[(*res)[i].ParentSpanId] + "-" + spanId2ServiceNameMap[(*res)[i].SpanId] - if _, ok := serviceMap[parent2childServiceName]; !ok { - serviceMap[parent2childServiceName] = &model.ServiceMapDependencyResponseItem{ - Parent: spanId2ServiceNameMap[(*res)[i].ParentSpanId], - Child: spanId2ServiceNameMap[(*res)[i].SpanId], - CallCount: 1, - } - } else { - serviceMap[parent2childServiceName].CallCount++ - } - } - - retMe := make([]model.ServiceMapDependencyResponseItem, 0, len(serviceMap)) - for _, dependency := range serviceMap { - if dependency.Parent == "" { - continue - } - retMe = append(retMe, *dependency) - } - - return &retMe, nil -} diff --git a/pkg/query-service/druidQuery/query.go b/pkg/query-service/druidQuery/query.go deleted file mode 100644 index 8196bb2b18..0000000000 --- a/pkg/query-service/druidQuery/query.go +++ /dev/null @@ -1,419 +0,0 @@ -package druidQuery - -import ( - "encoding/json" - "fmt" - "time" - - "go.signoz.io/query-service/constants" - "go.signoz.io/query-service/godruid" - "go.signoz.io/query-service/model" - "go.uber.org/zap" -) - -func check(e error) { - if e != nil { - panic(e) - } -} - -type DurationItem struct { - Value float32 `json:"value"` - QuantileAgg int `json:"quantile_agg"` -} - -type SpanSearchAggregatesDuratonReceivedItem struct { - Timestamp string `json:"timestamp"` - Result DurationItem `json:"result"` -} - -func buildFilters(queryParams *model.SpanSearchParams) (*godruid.Filter, error) { - - var filter *godruid.Filter - - if len(queryParams.ServiceName) != 0 { - filter = godruid.FilterSelector("ServiceName", queryParams.ServiceName) - } - - if len(queryParams.OperationName) != 0 { - - newFilter := godruid.FilterSelector("Name", queryParams.OperationName) - filter = godruid.FilterAnd(filter, newFilter) - - } - if len(queryParams.Kind) != 0 { - - newFilter := godruid.FilterSelector("Kind", queryParams.Kind) - filter = godruid.FilterAnd(filter, newFilter) - - } - - // zap.S().Debug("MinDuration: ", queryParams.MinDuration) - var lower string - var upper string - - if len(queryParams.MinDuration) != 0 { - lower = queryParams.MinDuration - } - if len(queryParams.MaxDuration) != 0 { - upper = queryParams.MaxDuration - } - - if len(lower) != 0 || len(upper) != 0 { - - newFilter := godruid.FilterBound("DurationNano", lower, upper, false, false, "numeric") - filter = godruid.FilterAnd(filter, newFilter) - - } - - for _, item := range queryParams.Tags { - - var newFilter *godruid.Filter - - if item.Operator == "equals" { - newFilter = godruid.FilterSelector("Tags", fmt.Sprintf("%s:%s", item.Key, item.Value)) - } else if item.Operator == "contains" { - valuesFilter := godruid.FilterSearch("TagsValues", fmt.Sprintf("%s", item.Value)) - keysFilter := godruid.FilterSelector("TagsKeys", fmt.Sprintf("%s", item.Key)) - newFilter = godruid.FilterAnd(valuesFilter, keysFilter) - } else if item.Operator == "isnotnull" { - newFilter = godruid.FilterSelector("TagsKeys", fmt.Sprintf("%s", item.Key)) - } else { - return nil, fmt.Errorf("Tag Operator %s not supported", item.Operator) - } - - if item.Key == "error" && item.Value == "true" { - statusCodeHttpFilter := godruid.FilterBound("StatusCode", "500", "600", false, true, "numeric") - statusCodeGrpcFilter := godruid.FilterBound("StatusCode", "2", "2", false, false, "numeric") - filterError := godruid.FilterOr(statusCodeHttpFilter, statusCodeGrpcFilter, newFilter) - filter = godruid.FilterAnd(filter, filterError) - continue - } - - filter = godruid.FilterAnd(filter, newFilter) - - } - - // if filter == nil { - // return nil, fmt.Errorf("No search criteria for spans was specified") - // } - return filter, nil - -} - -func buildFiltersForSpansAggregates(queryParams *model.SpanSearchAggregatesParams) (*godruid.Filter, error) { - - var filter *godruid.Filter - - if len(queryParams.ServiceName) != 0 { - filter = godruid.FilterSelector("ServiceName", queryParams.ServiceName) - } - - if len(queryParams.OperationName) != 0 { - - newFilter := godruid.FilterSelector("Name", queryParams.OperationName) - filter = godruid.FilterAnd(filter, newFilter) - - } - if len(queryParams.Kind) != 0 { - - newFilter := godruid.FilterSelector("Kind", queryParams.Kind) - filter = godruid.FilterAnd(filter, newFilter) - - } - - // zap.S().Debug("MinDuration: ", queryParams.MinDuration) - var lower string - var upper string - - if len(queryParams.MinDuration) != 0 { - lower = queryParams.MinDuration - } - if len(queryParams.MaxDuration) != 0 { - upper = queryParams.MaxDuration - } - - if len(lower) != 0 || len(upper) != 0 { - - newFilter := godruid.FilterBound("DurationNano", lower, upper, false, false, "numeric") - filter = godruid.FilterAnd(filter, newFilter) - - } - - for _, item := range queryParams.Tags { - - var newFilter *godruid.Filter - - if item.Operator == "equals" { - newFilter = godruid.FilterSelector("Tags", fmt.Sprintf("%s:%s", item.Key, item.Value)) - } else if item.Operator == "contains" { - valuesFilter := godruid.FilterSearch("TagsValues", fmt.Sprintf("%s", item.Value)) - keysFilter := godruid.FilterSelector("TagsKeys", fmt.Sprintf("%s", item.Key)) - newFilter = godruid.FilterAnd(valuesFilter, keysFilter) - } else if item.Operator == "isnotnull" { - newFilter = godruid.FilterSelector("TagsKeys", fmt.Sprintf("%s", item.Key)) - } else { - return nil, fmt.Errorf("Tag Operator %s not supported", item.Operator) - } - - if item.Key == "error" && item.Value == "true" { - statusCodeHttpFilter := godruid.FilterBound("StatusCode", "500", "600", false, true, "numeric") - statusCodeGrpcFilter := godruid.FilterBound("StatusCode", "2", "2", false, false, "numeric") - filterError := godruid.FilterOr(statusCodeHttpFilter, statusCodeGrpcFilter, newFilter) - filter = godruid.FilterAnd(filter, filterError) - continue - } - - filter = godruid.FilterAnd(filter, newFilter) - - } - - // newFilter := godruid.FilterSelector("Kind", "2") - // filter = godruid.FilterAnd(filter, newFilter) - - // if filter == nil { - // return nil, fmt.Errorf("No search criteria for spans was specified") - // } - return filter, nil - -} - -func SearchTraces(client *godruid.Client, traceId string) (*[]model.SearchSpansResult, error) { - - filter := godruid.FilterSelector("TraceId", traceId) - - query := &godruid.QueryScan{ - DataSource: constants.DruidDatasource, - Intervals: []string{"-146136543-09-08T08:23:32.096Z/146140482-04-24T15:36:27.903Z"}, - Filter: filter, - Columns: []string{"__time", "SpanId", "TraceId", "ServiceName", "Name", "Kind", "DurationNano", "TagsKeys", "TagsValues", "References"}, - Order: "none", - BatchSize: 20480, - } - - clientErr := client.Query(query) - // fmt.Println("requst", client.LastRequest) - if clientErr != nil { - // fmt.Println("Error: ", err) - zap.S().Error(zap.Error(clientErr)) - return nil, clientErr - } - - // fmt.Println("response", client.LastResponse) - - // fmt.Printf("query.QueryResult:\n%v", query.QueryResult) - - var searchSpansResult []model.SearchSpansResult - searchSpansResult = make([]model.SearchSpansResult, len(query.QueryResult)) - - searchSpansResult[0].Columns = make([]string, len(query.QueryResult[0].Columns)) - copy(searchSpansResult[0].Columns, query.QueryResult[0].Columns) - - searchSpansResult[0].Events = make([][]interface{}, len(query.QueryResult[0].Events)) - copy(searchSpansResult[0].Events, query.QueryResult[0].Events) - - return &searchSpansResult, nil - -} - -func SearchSpansAggregate(client *godruid.Client, queryParams *model.SpanSearchAggregatesParams) ([]model.SpanSearchAggregatesResponseItem, error) { - - filter, err := buildFiltersForSpansAggregates(queryParams) - var needsPostAggregation bool = true - - if err != nil { - return nil, err - } - - granularity := godruid.GranPeriod{ - Type: "period", - Period: queryParams.GranPeriod, - // Origin: queryParams.GranOrigin, - } - - var aggregation godruid.Aggregation - var postAggregation godruid.PostAggregation - - if queryParams.Dimension == "duration" { - switch queryParams.AggregationOption { - case "p50": - aggregationString := `{ "type": "quantilesDoublesSketch", "fieldName": "QuantileDuration", "name": "quantile_agg", "k": 128}` - aggregation = godruid.AggRawJson(aggregationString) - postAggregationString := `{"type":"quantilesDoublesSketchToQuantile","name":"value","field":{"type":"fieldAccess","fieldName":"quantile_agg"},"fraction":0.5}` - postAggregation = godruid.PostAggRawJson(postAggregationString) - break - case "p95": - aggregationString := `{ "type": "quantilesDoublesSketch", "fieldName": "QuantileDuration", "name": "quantile_agg", "k": 128}` - aggregation = godruid.AggRawJson(aggregationString) - postAggregationString := `{"type":"quantilesDoublesSketchToQuantile","name":"value","field":{"type":"fieldAccess","fieldName":"quantile_agg"},"fraction":0.95}` - postAggregation = godruid.PostAggRawJson(postAggregationString) - break - - case "p99": - aggregationString := `{ "type": "quantilesDoublesSketch", "fieldName": "QuantileDuration", "name": "quantile_agg", "k": 128}` - aggregation = godruid.AggRawJson(aggregationString) - postAggregationString := `{"type":"quantilesDoublesSketchToQuantile","name":"value","field":{"type":"fieldAccess","fieldName":"quantile_agg"},"fraction":0.99}` - postAggregation = godruid.PostAggRawJson(postAggregationString) - break - } - - } else if queryParams.Dimension == "calls" { - - aggregation = godruid.AggCount("value") - needsPostAggregation = false - } - var query *godruid.QueryTimeseries - if needsPostAggregation { - query = &godruid.QueryTimeseries{ - DataSource: constants.DruidDatasource, - Intervals: []string{queryParams.Intervals}, - Granularity: granularity, - Filter: filter, - Aggregations: []godruid.Aggregation{aggregation}, - PostAggregations: []godruid.PostAggregation{postAggregation}, - } - } else { - query = &godruid.QueryTimeseries{ - DataSource: constants.DruidDatasource, - Intervals: []string{queryParams.Intervals}, - Granularity: granularity, - Filter: filter, - Aggregations: []godruid.Aggregation{aggregation}, - // PostAggregations: []godruid.PostAggregation{postAggregation}, - } - } - - clientErr := client.Query(query) - // fmt.Println("requst", client.LastRequest) - if clientErr != nil { - // fmt.Println("Error: ", err) - zap.S().Error(zap.Error(clientErr)) - return nil, clientErr - } - - // fmt.Println("response", client.LastResponse) - - receivedResponse := new([]SpanSearchAggregatesDuratonReceivedItem) - err = json.Unmarshal([]byte(client.LastResponse), receivedResponse) - if err != nil && len(*receivedResponse) == 0 { - zap.S().Error(err) - return nil, fmt.Errorf("Error in unmarshalling response from druid") - } - - var response []model.SpanSearchAggregatesResponseItem - - for _, elem := range *receivedResponse { - - value := elem.Result.Value - timeObj, _ := time.Parse(time.RFC3339Nano, elem.Timestamp) - timestamp := timeObj.UnixNano() - - if queryParams.AggregationOption == "rate_per_sec" { - value = elem.Result.Value * 1.0 / float32(queryParams.StepSeconds) - } - response = append(response, model.SpanSearchAggregatesResponseItem{ - Timestamp: timestamp, - Value: value, - }) - } - return response, nil - - // fmt.Printf("query.QueryResult:\n%v", query.QueryResult) - - return nil, nil -} - -func SearchSpans(client *godruid.Client, queryParams *model.SpanSearchParams) (*[]model.SearchSpansResult, error) { - - filter, err := buildFilters(queryParams) - - if err != nil { - return nil, err - } - - query := &godruid.QueryScan{ - DataSource: constants.DruidDatasource, - Intervals: []string{queryParams.Intervals}, - Filter: filter, - Columns: []string{"__time", "SpanId", "TraceId", "ServiceName", "Name", "Kind", "DurationNano", "TagsKeys", "TagsValues"}, - Limit: queryParams.Limit, - Offset: queryParams.Offset, - Order: "descending", - BatchSize: 20480, - } - - clientErr := client.Query(query) - // fmt.Println("requst", client.LastRequest) - if clientErr != nil { - // fmt.Println("Error: ", err) - zap.S().Error(zap.Error(clientErr)) - return nil, clientErr - } - - // fmt.Println("response", client.LastResponse) - - // fmt.Printf("query.QueryResult:\n%v", query.QueryResult) - - var searchSpansResult []model.SearchSpansResult - searchSpansResult = make([]model.SearchSpansResult, len(query.QueryResult)) - - searchSpansResult[0].Columns = make([]string, len(query.QueryResult[0].Columns)) - copy(searchSpansResult[0].Columns, query.QueryResult[0].Columns) - - searchSpansResult[0].Events = make([][]interface{}, len(query.QueryResult[0].Events)) - copy(searchSpansResult[0].Events, query.QueryResult[0].Events) - - return &searchSpansResult, nil -} - -func GetApplicationPercentiles(client *godruid.Client, queryParams *model.ApplicationPercentileParams) ([]godruid.Timeseries, error) { - - // query := &godruid.QueryGroupBy{ - // DataSource: constants.DruidDatasource, - // Intervals: []string{"2020-12-11T05:23:00.000Z/2020-12-11T05:24:00.000Z"}, - // Granularity: godruid.GranMinute, - // Filter: godruid.FilterSelector("Kind", "2"), - // Dimensions: []godruid.DimSpec{"ServiceName"}, - // Aggregations: []godruid.Aggregation{godruid.AggRawJson(`{ "type" : "count", "name" : "count" }`)}, - // } - - granularity := godruid.GranPeriod{ - Type: "period", - Period: queryParams.GranPeriod, - Origin: queryParams.GranOrigin, - } - - filterKind := godruid.FilterSelector("Kind", "2") - filterService := godruid.FilterSelector("ServiceName", queryParams.ServiceName) - filter := godruid.FilterAnd(filterKind, filterService) - - aggregationString := `{ "type": "quantilesDoublesSketch", "fieldName": "QuantileDuration", "name": "quantile_agg", "k": 128}` - aggregation := godruid.AggRawJson(aggregationString) - - postAggregationString := `{"type":"quantilesDoublesSketchToQuantiles","name":"final_quantile","field":{"type":"fieldAccess","fieldName":"quantile_agg"},"fractions":[0.5,0.99]}` - postAggregation := godruid.PostAggRawJson(postAggregationString) - - query := &godruid.QueryTimeseries{ - DataSource: constants.DruidDatasource, - Intervals: []string{queryParams.Intervals}, - Granularity: granularity, - Filter: filter, - Aggregations: []godruid.Aggregation{aggregation}, - PostAggregations: []godruid.PostAggregation{postAggregation}, - } - - err := client.Query(query) - // fmt.Println("requst", client.LastRequest) - if err != nil { - // fmt.Println("Error: ", err) - zap.S().Error(zap.Error(err)) - return nil, err - } - - // fmt.Println("response", client.LastResponse) - - // fmt.Printf("query.QueryResult:\n%v", query.QueryResult) - - return query.QueryResult, nil - -} diff --git a/pkg/query-service/go.mod b/pkg/query-service/go.mod index 50d11e9d91..b101f18da5 100644 --- a/pkg/query-service/go.mod +++ b/pkg/query-service/go.mod @@ -1,79 +1,142 @@ module go.signoz.io/query-service -go 1.14 +go 1.17 require ( - cloud.google.com/go v0.88.0 // indirect - github.com/ClickHouse/clickhouse-go v1.5.4 - github.com/Microsoft/go-winio v0.5.1 // indirect - github.com/OneOfOne/xxhash v1.2.8 // indirect - github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect - github.com/aws/aws-sdk-go v1.27.0 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/containerd/containerd v1.4.12 // indirect - github.com/dhui/dktest v0.3.4 // indirect - github.com/docker/docker v20.10.12+incompatible // indirect - github.com/frankban/quicktest v1.13.0 // indirect + github.com/ClickHouse/clickhouse-go/v2 v2.0.12 github.com/go-kit/log v0.1.0 - github.com/golang-migrate/migrate/v4 v4.14.1 - github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.3.0 github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 github.com/gosimple/slug v1.10.0 - github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect - github.com/hashicorp/go-cleanhttp v0.5.0 // indirect - github.com/hashicorp/go-immutable-radix v1.0.0 // indirect - github.com/hashicorp/go-msgpack v1.1.5 // indirect - github.com/hashicorp/go-sockaddr v1.0.0 // indirect - github.com/hashicorp/go-uuid v1.0.2 // indirect - github.com/hashicorp/memberlist v0.1.0 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmoiron/sqlx v1.3.4 github.com/json-iterator/go v1.1.10 - github.com/kr/text v0.2.0 // indirect - github.com/lib/pq v1.10.0 // indirect github.com/mattn/go-sqlite3 v1.14.8 - github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect - github.com/minio/minio-go v6.0.14+incompatible github.com/minio/minio-go/v6 v6.0.57 - github.com/mitchellh/go-testing-interface v1.14.1 // indirect - github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/oklog/oklog v0.3.2 - github.com/oklog/run v1.1.0 // indirect - github.com/onsi/gomega v1.14.0 // indirect - github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/opentracing/opentracing-go v1.1.0 // indirect - github.com/pascaldekloe/goe v0.1.0 // indirect - github.com/pierrec/lz4 v2.4.1+incompatible // indirect github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v0.9.0-pre1.0.20181001174001-0a8115f42e03 github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1 - github.com/prometheus/procfs v0.0.8 // indirect github.com/prometheus/prometheus v2.5.0+incompatible + github.com/prometheus/tsdb v0.0.0-20181003080831-0ce41118ed20 github.com/rs/cors v1.7.0 + github.com/smartystreets/goconvey v1.6.4 + github.com/soheilhy/cmux v0.1.4 + go.uber.org/zap v1.16.0 + gopkg.in/segmentio/analytics-go.v3 v3.1.0 + k8s.io/apimachinery v0.0.0-20180621070125-103fd098999d +) + +require ( + github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect + github.com/klauspost/cpuid v1.2.3 // indirect + github.com/minio/md5-simd v1.1.0 // indirect + github.com/minio/sha256-simd v0.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/ini.v1 v1.42.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) + +require ( + cloud.google.com/go v0.88.0 // indirect + github.com/Azure/azure-sdk-for-go v5.0.0-beta.0.20161028183111-bd73d950fa44+incompatible // indirect + github.com/Azure/go-autorest v10.8.1+incompatible // indirect + github.com/OneOfOne/xxhash v1.2.8 // indirect + github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect + github.com/auth0/go-jwt-middleware v1.0.1 + github.com/aws/aws-sdk-go v1.27.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect + github.com/felixge/httpsnoop v1.0.1 // indirect + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-kit/kit v0.4.1-0.20170517165212-6964666de57c // indirect + github.com/go-logfmt/logfmt v0.5.0 // indirect + github.com/go-stack/stack v1.8.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/golang/glog v0.0.0-20210429001901-424d2337a529 // indirect + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/btree v1.0.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/googleapis/gax-go/v2 v2.0.5 // indirect + github.com/googleapis/gnostic v0.2.3-0.20180520015035-48a0ecefe2e4 // indirect + github.com/gophercloud/gophercloud v0.0.0-20170607034829-caf34a65f602 // indirect + github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect + github.com/gosimple/unidecode v1.0.0 // indirect + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/hashicorp/consul v1.1.1-0.20180615161029-bed22a81e9fd // indirect + github.com/hashicorp/go-cleanhttp v0.5.0 // indirect + github.com/hashicorp/go-immutable-radix v1.0.0 // indirect + github.com/hashicorp/go-msgpack v1.1.5 // indirect + github.com/hashicorp/go-multierror v1.1.0 // indirect + github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90 // indirect + github.com/hashicorp/go-sockaddr v1.0.0 // indirect + github.com/hashicorp/go-uuid v1.0.2 // indirect + github.com/hashicorp/golang-lru v0.5.1 // indirect + github.com/hashicorp/memberlist v0.1.0 // indirect + github.com/hashicorp/serf v0.8.1-0.20161007004122-1d4fa605f6ff // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/jtolds/gls v4.20.0+incompatible // indirect + github.com/kr/text v0.2.0 // indirect + github.com/lib/pq v1.10.0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/miekg/dns v1.0.4 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect + github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 // indirect + github.com/oklog/run v1.1.0 // indirect + github.com/oklog/ulid v0.3.1-0.20170117200651-66bb6560562f // indirect + github.com/opentracing/opentracing-go v1.1.0 // indirect + github.com/pascaldekloe/goe v0.1.0 // indirect + github.com/paulmach/orb v0.4.0 // indirect + github.com/peterbourgon/diskv v2.0.2-0.20180312054125-0646ccaebea1+incompatible // indirect + github.com/pierrec/lz4/v4 v4.1.14 // indirect + github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect + github.com/prometheus/procfs v0.0.8 // indirect github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/segmentio/backo-go v1.0.0 // indirect - github.com/smartystreets/goconvey v1.6.4 - github.com/soheilhy/cmux v0.1.4 + github.com/shopspring/decimal v1.3.1 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/smartystreets/assertions v1.1.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/stretchr/testify v1.7.0 + github.com/spf13/pflag v1.0.3 // indirect + github.com/stretchr/testify v1.7.1 github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect - go.uber.org/zap v1.16.0 - golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect + go.opencensus.io v0.23.0 // indirect + go.opentelemetry.io/otel v1.4.1 // indirect + go.opentelemetry.io/otel/trace v1.4.1 // indirect + go.uber.org/atomic v1.6.0 // indirect + go.uber.org/multierr v1.5.0 // indirect + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 golang.org/x/net v0.0.0-20211013171255-e13a2654a71e // indirect - golang.org/x/sys v0.0.0-20211013075003-97ac67df715c // indirect + golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 // indirect + golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect golang.org/x/text v0.3.7 // indirect + golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect google.golang.org/api v0.51.0 // indirect + google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20211013025323-ce878158c4d4 // indirect - google.golang.org/grpc v1.41.0 // indirect + google.golang.org/grpc v1.41.0 google.golang.org/grpc/examples v0.0.0-20210803221256-6ba56c814be7 // indirect + google.golang.org/protobuf v1.27.1 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect - gopkg.in/segmentio/analytics-go.v3 v3.1.0 - gotest.tools/v3 v3.1.0 // indirect - + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/api v0.0.0-20180628040859-072894a440bd // indirect + k8s.io/client-go v8.0.0+incompatible // indirect ) -replace github.com/prometheus/prometheus => github.com/SigNoz/prometheus v1.9.69 +replace github.com/prometheus/prometheus => github.com/SigNoz/prometheus v1.9.70 diff --git a/pkg/query-service/go.sum b/pkg/query-service/go.sum index 07ffe02d8c..77fd087ceb 100644 --- a/pkg/query-service/go.sum +++ b/pkg/query-service/go.sum @@ -12,8 +12,6 @@ cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bP cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.63.0/go.mod h1:GmezbQc7T2snqkEXWfZ0sy0VfkB/ivI2DdtJL2DEmlg= -cloud.google.com/go v0.64.0/go.mod h1:xfORb36jGvE+6EexW71nMEtL025s3x6xvuYUKM4JLv4= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= @@ -37,7 +35,6 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/spanner v1.9.0/go.mod h1:xvlEn0NZ5v1iJPYsBnUVRDNvccDxsBTEi16pJRKQVws= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= @@ -46,46 +43,37 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go v5.0.0-beta.0.20161028183111-bd73d950fa44+incompatible h1:+5hx+ZckahrubYyxbjTwnq9w5xpnq1CwSL4N54I8/qc= github.com/Azure/azure-sdk-for-go v5.0.0-beta.0.20161028183111-bd73d950fa44+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v10.8.1+incompatible h1:u0jVQf+a6k6x8A+sT60l6EY9XZu+kHdnZVPAYqpVRo0= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/ClickHouse/clickhouse-go v1.3.12/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= -github.com/ClickHouse/clickhouse-go v1.4.5/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= -github.com/ClickHouse/clickhouse-go v1.5.4 h1:cKjXeYLNWVJIx2J1K6H2CqyRmfwVJVY1OV1coaaFcI0= -github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= -github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= -github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/ClickHouse/clickhouse-go v1.5.3 h1:Vok8zUb/wlqc9u8oEqQzBMBRDoFd8NxPRqgYEqMnV88= +github.com/ClickHouse/clickhouse-go v1.5.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= +github.com/ClickHouse/clickhouse-go/v2 v2.0.12 h1:Nbl/NZwoM6LGJm7smNBgvtdr/rxjlIssSW3eG/Nmb9E= +github.com/ClickHouse/clickhouse-go/v2 v2.0.12/go.mod h1:u4RoNQLLM2W6hNSPYrIESLJqaWSInZVmfM+MlaAhXcg= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= -github.com/SigNoz/prometheus v1.9.69 h1:DJogqPVErWpXG4rFz8euICdS7x/GFmMKRT7f4MJVw1I= -github.com/SigNoz/prometheus v1.9.69/go.mod h1:BHEawFYBYkVr9BjPXsz9Ye6QVVQ+2a99m6r/S8hO/lA= +github.com/SigNoz/prometheus v1.9.70 h1:0214i78cje5MkX0tXYwX2cK4cHXrFw18WcSLhv4YDpk= +github.com/SigNoz/prometheus v1.9.70/go.mod h1:Y4J9tGDmacMC+EcOTp+EIAn2C1sN+9kE+idyVKadiVM= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apache/arrow/go/arrow v0.0.0-20200601151325-b2287a20f230/go.mod h1:QNYViu/X0HXDHw7m3KXzWSVXIbfUvJqBFe6Gj8/pYA0= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/auth0/go-jwt-middleware v1.0.1 h1:/fsQ4vRr4zod1wKReUH+0A3ySRjGiT9G34kypO/EKwI= +github.com/auth0/go-jwt-middleware v1.0.1/go.mod h1:YSeUX3z6+TF2H+7padiEqNJ73Zy9vXW72U//IgN0BIM= github.com/aws/aws-sdk-go v1.13.44-0.20180507225419-00862f899353/go.mod h1:ZRmQr0FajVIyZ4ZzBYKG5P3ZqPz9IHG41ZoMu1ADI3k= -github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0 h1:0xphMHGMLBrPMfxR2AmVjZKcMEESEgWF8Kru94BNByk= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/beorn7/perks v0.0.0-20160229213445-3ac7bf7a47d1/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= -github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk= github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v0.0.0-20161118035902-4a94f899c20b/go.mod h1:fX/lfQBkSCDXZSUgv6jVIu/EVA3/JNseAX5asI4c4T4= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= @@ -95,52 +83,23 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg= github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/cmux v0.0.0-20170110192607-30d10be49292/go.mod h1:qRiX68mZX1lGBkTWyp3CLcenw9I94W2dLeRvMzcn9N4= github.com/cockroachdb/cockroach v0.0.0-20170608034007-84bc9597164f/go.mod h1:xeT/CQ0qZHangbYbWShlCGAx31aV4AjGswDUjhKS6HQ= -github.com/cockroachdb/cockroach-go v0.0.0-20190925194419-606b3d062051/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= -github.com/containerd/containerd v1.4.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.12 h1:V+SHzYmhng/iju6M5nFrpTTusrhidoxKTwdwLw+u4c4= -github.com/containerd/containerd v1.4.12/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/denisenkom/go-mssqldb v0.0.0-20200620013148-b91950f658ec/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.0.1-0.20161101193935-9ed569b5d1ac+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-bits v0.0.0-20160601073636-2ad8d707cc05/go.mod h1:/9UYwwvZuEgp+mQ4960SHWCU1FS+FgdFX+m5ExFByNs= -github.com/dhui/dktest v0.3.3/go.mod h1:EML9sP4sqJELHn4jV7B0TY8oF6077nk83/tz7M56jcQ= -github.com/dhui/dktest v0.3.4 h1:VbUEcaSP+U2/yUr9d2JhSThXYEnDlGabRSHe2rIE46E= -github.com/dhui/dktest v0.3.4/go.mod h1:4m4n6lmXlmVfESth7mzdcv8nBI5mOb5UROPqjM02csU= -github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v17.12.0-ce-rc1.0.20200618181300-9dc6525e6118+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v17.12.0-ce-rc1.0.20210128214336-420b1d36250f+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v20.10.12+incompatible h1:CEeNmFM0QZIsJCZKMkZx0ZcahTiewkrgiwfYD+dfl1U= -github.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -152,18 +111,15 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= -github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-ini/ini v1.21.1 h1:+QXUYsI7Tfxc64oD6R5BxU/Aq+UwGkyjH4W/hMNG7bg= github.com/go-ini/ini v1.21.1/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.4.1-0.20170517165212-6964666de57c h1:lGtNy7NU/+ytYPPneoErOaNrYkF5DOVCYViUK/7t7XA= github.com/go-kit/kit v0.4.1-0.20170517165212-6964666de57c/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -172,22 +128,19 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.5.4/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= -github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= -github.com/gogo/protobuf v0.0.0-20171123125729-971cbfd2e72b/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-migrate/migrate/v4 v4.14.1 h1:qmRd/rNGjM1r3Ve5gHd5ZplytrD02UcItYNxJ3iUHHE= -github.com/golang-migrate/migrate/v4 v4.14.1/go.mod h1:l7Ks0Au6fYHuUIxUhQ0rcVX1uLlJg54C/VvW7tvxSz0= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20210429001901-424d2337a529 h1:2voWjNECnrZRbfwXxHB1/j8wa6xdKn85B5NzgVL/pTU= github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -204,7 +157,6 @@ github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -224,8 +176,6 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20160529050041-d9eb7a3d35ec/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -233,7 +183,6 @@ github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Z github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -245,12 +194,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= -github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -274,7 +220,6 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210715191844-86eeefc3e471/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -285,17 +230,16 @@ github.com/googleapis/gnostic v0.2.3-0.20180520015035-48a0ecefe2e4 h1:Z09Qt6AGDt github.com/googleapis/gnostic v0.2.3-0.20180520015035-48a0ecefe2e4/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/gophercloud/gophercloud v0.0.0-20170607034829-caf34a65f602 h1:Acc1d6mIuURCyYN6nkm1d7+Gycfq1+jUWdnBbTyGb6E= github.com/gophercloud/gophercloud v0.0.0-20170607034829-caf34a65f602/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosimple/slug v1.10.0 h1:3XbiQua1IpCdrvuntWvGBxVm+K99wCSxJjlxkP49GGQ= github.com/gosimple/slug v1.10.0/go.mod h1:MICb3w495l9KNdZm+Xn5b6T2Hn831f9DMxiJ1r+bAjw= github.com/gosimple/unidecode v1.0.0 h1:kPdvM+qy0tnk4/BrnkrbdJ82xe88xn7c9hcaipDz4dQ= @@ -305,7 +249,6 @@ github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJr github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/consul v1.1.1-0.20180615161029-bed22a81e9fd h1:u6o+bd6FHxDKoCSa8PJ5vrHhAYSKgJtAHQtLO1EYgos= github.com/hashicorp/consul v1.1.1-0.20180615161029-bed22a81e9fd/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= @@ -334,34 +277,9 @@ github.com/hashicorp/memberlist v0.1.0 h1:qSsCiC0WYD39lbSitKNt40e30uorm2Ss/d4JGU github.com/hashicorp/memberlist v0.1.0/go.mod h1:ncdBp14cuox2iFOq3kDiquKU6fqsTBc3W6JvZwjxxsE= github.com/hashicorp/serf v0.8.1-0.20161007004122-1d4fa605f6ff h1:epPiU3hEuHbpThFTQSGbdBBJemXM7aNQIU1thmpucTU= github.com/hashicorp/serf v0.8.1-0.20161007004122-1d4fa605f6ff/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/influxdata/influxdb v1.2.3-0.20170331210902-15e594fc09f1/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.3.2/go.mod h1:LvCquS3HbBKwgl7KbX9KyqEIumJAbm1UMcTvGaIf3bM= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= @@ -380,41 +298,24 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.1.1-0.20150905172533-109e267447e9/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= -github.com/k0kubun/pp v2.3.0+incompatible/go.mod h1:GWse8YhT0p8pT4ir3ZgBbfZild3tgzSScAn6HmfYukg= -github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs= github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.0 h1:Zx5DJFEYQXio93kgXnQ09fXNiUKsqv4OUEu2UtGcB1E= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU= github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= @@ -425,8 +326,6 @@ github.com/miekg/dns v1.0.4 h1:Ec3LTJwwzqT1++63P12fhtdEbQhtPE7TBdD6rlhqrMM= github.com/miekg/dns v1.0.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4= github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= -github.com/minio/minio-go v6.0.14+incompatible h1:fnV+GD28LeqdN6vT2XdGKW8Qe/IfjJDswNVuni6km9o= -github.com/minio/minio-go v6.0.14+incompatible/go.mod h1:7guKYtitv8dktvNUGrhzmNlA5wrAABTQXCoesZdFQO8= github.com/minio/minio-go/v6 v6.0.57 h1:ixPkbKkyD7IhnluRgQpGSpHdpvNVaW6OD5R9IAO/9Tw= github.com/minio/minio-go/v6 v6.0.57/go.mod h1:5+R/nM9Pwrh0vqF+HbYYDQ84wdUFPyXHkrdT4AIkifM= github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= @@ -436,24 +335,14 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= -github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/mkevac/debugcharts v0.0.0-20191222103121-ae1c48aa8615/go.mod h1:Ad7oeElCZqA1Ufj0U9/liOF4BtVepxRcTvr2ey7zTvM= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mutecomm/go-sqlcipher/v4 v4.4.0/go.mod h1:PyN04SaWalavxRGH9E8ZftG6Ju7rsPrGmQRjrEaVpiY= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= -github.com/neo4j/neo4j-go-driver v1.8.1-0.20200803113522-b626aa943eba/go.mod h1:ncO5VaFWh0Nrt+4KT4mOZboaczBZcLuHrG+/sUeP8gI= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/oklog v0.2.3-0.20170918173356-f857583a70c3/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/oklog v0.3.2 h1:wVfs8F+in6nTBMkA7CbRw+zZMIB7nNM825cM1wuzoTk= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= @@ -461,34 +350,22 @@ github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/oklog/ulid v0.3.1-0.20170117200651-66bb6560562f h1:tt7Qj+4Pic1KiUqT7XNMnbAE3TLJAGH+5LMuX4roYbE= github.com/oklog/ulid v0.3.1-0.20170117200651-66bb6560562f/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.14.0 h1:ep6kpPVwmr/nTbklSx2nrLNSIO62DoYAhnPNIMhK8gI= -github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opentracing-contrib/go-stdlib v0.0.0-20170113013457-1de4cc2120e7/go.mod h1:PLldrQSroqzH70Xl+1DQcGnefIbqsKR7UDaiux3zV+w= github.com/opentracing/opentracing-go v1.0.1/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/paulmach/orb v0.4.0 h1:ilp1MQjRapLJ1+qcays1nZpe0mvkCY+b8JU/qBKRZ1A= +github.com/paulmach/orb v0.4.0/go.mod h1:FkcWtplUAIVqAuhAOV2d3rpbnQyliDOjOcLW9dUrfdU= +github.com/paulmach/protoscan v0.2.1-0.20210522164731-4e53c6875432/go.mod h1:2sV+uZ/oQh66m4XJVZm5iqUZ62BN88Ex1E+TTS0nLzI= github.com/peterbourgon/diskv v2.0.2-0.20180312054125-0646ccaebea1+incompatible h1:FhnA4iH8T/yYW+AolPONZjGE897wxj3MAzfEbrZkSYw= github.com/peterbourgon/diskv v2.0.2-0.20180312054125-0646ccaebea1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= +github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pierrec/lz4 v2.4.1+incompatible h1:mFe7ttWaflA46Mhqh+jUfjp2qTbPYxLB2/OyBppH9dg= -github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= +github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= +github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.8.1-0.20161029093637-248dadf4e906/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -506,40 +383,33 @@ github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLk github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/tsdb v0.0.0-20181003080831-0ce41118ed20 h1:Jh/eKJuru9z9u3rUGdQ8gYc3aZmCGkjXT3gmy0Ex8W8= github.com/prometheus/tsdb v0.0.0-20181003080831-0ce41118ed20/go.mod h1:lFf/o1J2a31WmWQbxYXfY1azJK5Xp5D8hwKMnVMBTGU= -github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/samuel/go-zookeeper v0.0.0-20161028232340-1d7be4effb13/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da h1:p3Vo3i64TCLY7gIfzeQaUJ+kppEO5WQG3cL8iE8tGHU= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/backo-go v1.0.0 h1:kbOAtGJY2DqOR0jfRkYEorx/b18RgtepGtY3+Cpe6qA= github.com/segmentio/backo-go v1.0.0/go.mod h1:kJ9mm9YmoWSkk+oQ+5Cj8DEoRCX2JT6As4kEtIIOp1M= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shirou/gopsutil v2.19.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/vfsgen v0.0.0-20180711163814-62bca832be04/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0= +github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/snowflakedb/glog v0.0.0-20180824191149-f5055e6f21ce/go.mod h1:EB/w24pR5VKI60ecFnKqXzxX3dOorz1rnVicQTQrGM0= -github.com/snowflakedb/gosnowflake v1.3.5/go.mod h1:13Ky+lxzIm3VqNDZJdyvu9MCGy+WgRdYFdXp96UcLZU= github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -549,20 +419,16 @@ github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v1.2.0/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= -github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= -github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g= github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -570,9 +436,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= -go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -581,31 +444,25 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/otel v1.4.1 h1:QbINgGDDcoQUoMJa2mMaWno49lja9sHwp6aoa2n3a4g= +go.opentelemetry.io/otel v1.4.1/go.mod h1:StM6F/0fSwpd8dKWDCdRr7uRvEPYdW0hBSlbdTiUde4= +go.opentelemetry.io/otel/trace v1.4.1 h1:O+16qcdTrT7zxv2J6GejTPFinSwA++cYerC5iSiF8EQ= +go.opentelemetry.io/otel/trace v1.4.1/go.mod h1:iYEVbroFCNut9QkwEczV9vMRPHNKSSwYZjulEtsmhFc= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -645,14 +502,10 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190225153610-fe579d43d832/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -662,7 +515,6 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -673,15 +525,11 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -689,13 +537,10 @@ golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211013171255-e13a2654a71e h1:Xj+JO91noE97IN6F/7WZxzC5QE6yENAQPrwIYhW3bsA= golang.org/x/net v0.0.0-20211013171255-e13a2654a71e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -721,14 +566,9 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180224232135-f6cff0780e54/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -736,14 +576,11 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220220014-0732a990476f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -758,16 +595,12 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -780,8 +613,8 @@ golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211013075003-97ac67df715c h1:taxlMj0D/1sOAuv/CbSD+MMDof2vbyPTqz5FNYKpXt8= -golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 h1:WecRHqgE09JBkh/584XIE6PMz5KKE/vER4izNUi30AQ= +golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -801,7 +634,6 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= @@ -810,15 +642,12 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190424220101-1e8e1cfdf96b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -848,16 +677,11 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200806022845-90696ccdc692/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200817023811-d00afeaade8f/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200818005847-188abfa75333/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= @@ -867,8 +691,6 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -901,9 +723,7 @@ google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtuk google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0 h1:SQaA2Cx57B+iPw2MBgyjEkoeMkRK2IenSGoia0U3lCk= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/appengine v1.0.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= @@ -942,16 +762,12 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200911024640-645f7a48b24f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201030142918-24207fddd1c3/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210207032614-bba0dbe2a9ea/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -980,7 +796,6 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= @@ -1017,34 +832,23 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify/fsnotify.v1 v1.3.0/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/segmentio/analytics-go.v3 v3.1.0 h1:UzxH1uaGZRpMKDhJyBz0pexz6yUoBU3x8bJsRk/HV6U= gopkg.in/segmentio/analytics-go.v3 v3.1.0/go.mod h1:4QqqlTlSSpVlWA9/9nDcPw+FkM2yv1NQoYjUbL9/JAw= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.1.0 h1:rVV8Tcg/8jHUkPUorwjaMTtemIMVXfIPKiOqnhEhakk= -gotest.tools/v3 v3.1.0/go.mod h1:fHy7eyTmJFO5bQbUsEGQ1v4m2J3Jz9eWL54TP2/ZuYQ= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1061,18 +865,6 @@ k8s.io/client-go v8.0.0+incompatible h1:tTI4hRmb1DRMl4fG6Vclfdi6nTM82oIrTT7Hfitm k8s.io/client-go v8.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= k8s.io/kube-openapi v0.0.0-20180629012420-d83b052f768a h1:tHgpQvrWaYfrnC8G4N0Oszw5HHCsZxKilDi2R7HuCSM= k8s.io/kube-openapi v0.0.0-20180629012420-d83b052f768a/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= -modernc.org/b v1.0.0/go.mod h1:uZWcZfRj1BpYzfN9JTerzlNUnnPsV9O2ZA8JsRcubNg= -modernc.org/db v1.0.0/go.mod h1:kYD/cO29L/29RM0hXYl4i3+Q5VojL31kTUVpVJDw0s8= -modernc.org/file v1.0.0/go.mod h1:uqEokAEn1u6e+J45e54dsEA/pw4o7zLrA2GwyntZzjw= -modernc.org/fileutil v1.0.0/go.mod h1:JHsWpkrk/CnVV1H/eGlFf85BEpfkrp56ro8nojIq9Q8= -modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= -modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM= -modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8= -modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= -modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY= -modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k= -modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= -modernc.org/zappy v1.0.0/go.mod h1:hHe+oGahLVII/aTTyWK/b53VDHMAGCBYYeZ9sn83HC4= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/pkg/query-service/godruid/.gitignore b/pkg/query-service/godruid/.gitignore deleted file mode 100644 index daf913b1b3..0000000000 --- a/pkg/query-service/godruid/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test -*.prof diff --git a/pkg/query-service/godruid/LICENSE b/pkg/query-service/godruid/LICENSE deleted file mode 100644 index b6a9fe07c1..0000000000 --- a/pkg/query-service/godruid/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 shunfei - -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. - diff --git a/pkg/query-service/godruid/README.md b/pkg/query-service/godruid/README.md deleted file mode 100644 index 58a8a21515..0000000000 --- a/pkg/query-service/godruid/README.md +++ /dev/null @@ -1,9 +0,0 @@ -godruid -======= - -An open source Golang client for Druid - - -**Node:** This project is not yet beta released. It should certainly contains bugs, and the apis could change in any time. So please be really careful if you choose to use it in production. Currently the [groupBy](http://druid.io/docs/latest/GroupByQuery.html) and [topN](http://druid.io/docs/latest/TopNQuery.html) queries are used in our production. - -Any bug fixes, issues, contributions, questions are welcome. \ No newline at end of file diff --git a/pkg/query-service/godruid/aggregations.go b/pkg/query-service/godruid/aggregations.go deleted file mode 100644 index e34126d55c..0000000000 --- a/pkg/query-service/godruid/aggregations.go +++ /dev/null @@ -1,85 +0,0 @@ -package godruid - -import ( - "encoding/json" -) - -type Aggregation struct { - Type string `json:"type"` - Name string `json:"name,omitempty"` - FieldName string `json:"fieldName,omitempty"` - FieldNames []string `json:"fieldNames,omitempty"` - FnAggregate string `json:"fnAggregate,omitempty"` - FnCombine string `json:"fnCombine,omitempty"` - FnReset string `json:"fnReset,omitempty"` - ByRow bool `json:"byRow,omitempty"` -} - -func AggRawJson(rawJson string) Aggregation { - agg := &Aggregation{} - json.Unmarshal([]byte(rawJson), agg) - return *agg -} - -func AggCount(name string) Aggregation { - return Aggregation{ - Type: "count", - Name: name, - } -} - -func AggLongSum(name, fieldName string) Aggregation { - return Aggregation{ - Type: "longSum", - Name: name, - FieldName: fieldName, - } -} - -func AggDoubleSum(name, fieldName string) Aggregation { - return Aggregation{ - Type: "doubleSum", - Name: name, - FieldName: fieldName, - } -} - -func AggMin(name, fieldName string) Aggregation { - return Aggregation{ - Type: "min", - Name: name, - FieldName: fieldName, - } -} - -func AggMax(name, fieldName string) Aggregation { - return Aggregation{ - Type: "max", - Name: name, - FieldName: fieldName, - } -} - -func AggJavaScript(name, fnAggregate, fnCombine, fnReset string, fieldNames []string) Aggregation { - return Aggregation{ - Type: "javascript", - Name: name, - FieldNames: fieldNames, - FnAggregate: fnAggregate, - FnCombine: fnCombine, - FnReset: fnReset, - } -} - -func AggCardinality(name string, fieldNames []string, byRow ...bool) Aggregation { - isByRow := false - if len(byRow) != 0 { - isByRow = byRow[0] - } - return Aggregation{ - Type: "cardinality", - Name: name, - FieldNames: fieldNames, - ByRow: isByRow, - } -} diff --git a/pkg/query-service/godruid/client.go b/pkg/query-service/godruid/client.go deleted file mode 100644 index 4733686765..0000000000 --- a/pkg/query-service/godruid/client.go +++ /dev/null @@ -1,97 +0,0 @@ -package godruid - -import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "time" - - "go.uber.org/zap" -) - -const ( - DefaultEndPoint = "/druid/v2" -) - -type Client struct { - Url string - EndPoint string - Timeout time.Duration - - Debug bool - LastRequest string - LastResponse string -} - -func (c *Client) Query(query Query) (err error) { - query.setup() - var reqJson []byte - - if c.Debug { - reqJson, err = json.MarshalIndent(query, "", " ") - } else { - reqJson, err = json.Marshal(query) - } - if err != nil { - return - } - - // log request made to druid - zap.S().Info(string(reqJson)) - - result, err := c.QueryRaw(reqJson) - if err != nil { - return - } - - return query.onResponse(result) -} - -func (c *Client) QueryRaw(req []byte) (result []byte, err error) { - if c.EndPoint == "" { - c.EndPoint = DefaultEndPoint - } - endPoint := c.EndPoint - if c.Debug { - endPoint += "?pretty" - c.LastRequest = string(req) - } - if err != nil { - return - } - - // By default, use 60 second timeout unless specified otherwise - // by the caller - clientTimeout := 60 * time.Second - if c.Timeout != 0 { - clientTimeout = c.Timeout - } - - httpClient := &http.Client{ - Timeout: clientTimeout, - } - - resp, err := httpClient.Post(c.Url+endPoint, "application/json", bytes.NewBuffer(req)) - if err != nil { - return - } - defer func() { - resp.Body.Close() - }() - - result, err = ioutil.ReadAll(resp.Body) - if err != nil { - return - } - if c.Debug { - c.LastResponse = string(result) - } - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("%s: %s", resp.Status, string(result)) - } - - return -} diff --git a/pkg/query-service/godruid/client_test.go b/pkg/query-service/godruid/client_test.go deleted file mode 100644 index 495c2f3d76..0000000000 --- a/pkg/query-service/godruid/client_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package godruid - -import ( - "fmt" - . "github.com/smartystreets/goconvey/convey" - "testing" -) - -func TestGroupby(t *testing.T) { - Convey("TestGroupby", t, func() { - query := &QueryGroupBy{ - DataSource: "campaign", - Intervals: []string{"2014-09-01T00:00/2020-01-01T00"}, - Granularity: GranAll, - Filter: FilterAnd(FilterJavaScript("hour", "function(x) { return(x >= 1) }"), nil), - LimitSpec: LimitDefault(5), - Dimensions: []DimSpec{"campaign_id"}, - Aggregations: []Aggregation{AggRawJson(`{ "type" : "count", "name" : "count" }`), AggLongSum("impressions", "impressions")}, - PostAggregations: []PostAggregation{PostAggArithmetic("imp/count", "/", []PostAggregation{ - PostAggFieldAccessor("impressions"), - PostAggRawJson(`{ "type" : "fieldAccess", "fieldName" : "count" }`)})}, - } - client := Client{ - Url: "http://192.168.10.60:8009", - Debug: true, - } - - err := client.Query(query) - fmt.Println("requst", client.LastRequest) - So(err, ShouldEqual, nil) - - fmt.Println("response", client.LastResponse) - - fmt.Printf("query.QueryResult:\n%v", query.QueryResult) - - }) -} - -func TestSearch(t *testing.T) { - // return - Convey("TestSearch", t, func() { - query := &QuerySearch{ - DataSource: "campaign", - Intervals: []string{"2014-09-01T00:00/2020-01-01T00"}, - Granularity: GranAll, - SearchDimensions: []string{"campaign_id", "hour"}, - Query: SearchQueryInsensitiveContains(1313), - Sort: SearchSortLexicographic, - } - client := Client{ - Url: "http://192.168.10.60:8009", - Debug: true, - } - - err := client.Query(query) - So(err, ShouldEqual, nil) - - fmt.Println("requst", client.LastRequest) - fmt.Println("response", client.LastResponse) - - fmt.Printf("query.QueryResult:\n%v", query.QueryResult) - - }) -} diff --git a/pkg/query-service/godruid/dimension_spec.go b/pkg/query-service/godruid/dimension_spec.go deleted file mode 100644 index 0d867a53c3..0000000000 --- a/pkg/query-service/godruid/dimension_spec.go +++ /dev/null @@ -1,72 +0,0 @@ -package godruid - -type DimSpec interface{} - -type Dimension struct { - Type string `json:"type"` - Dimension string `json:"dimension"` - OutputName string `json:"outputName"` - DimExtractionFn *DimExtractionFn `json:"dimExtractionFn,omitempty"` -} - -type DimExtractionFn struct { - Type string `json:"type"` - Expr string `json:"expr,omitempty"` - Query *SearchQuery `json:"query,omitempty"` - TimeFormat string `json:"timeFormat,omitempty"` - ResultFormat string `json:"resultFormat,omitempty"` - Function string `json:"function,omitempty"` -} - -func DimDefault(dimension, outputName string) DimSpec { - return &Dimension{ - Type: "default", - Dimension: dimension, - OutputName: outputName, - } -} - -func DimExtraction(dimension, outputName string, fn *DimExtractionFn) DimSpec { - return &Dimension{ - Type: "extraction", - Dimension: dimension, - OutputName: outputName, - DimExtractionFn: fn, - } -} - -func DimExFnRegex(expr string) *DimExtractionFn { - return &DimExtractionFn{ - Type: "regex", - Expr: expr, - } -} - -func DimExFnPartial(expr string) *DimExtractionFn { - return &DimExtractionFn{ - Type: "partial", - Expr: expr, - } -} - -func DimExFnSearchQuerySpec(query *SearchQuery) *DimExtractionFn { - return &DimExtractionFn{ - Type: "searchQuery", - Query: query, - } -} - -func DimExFnTime(timeFormat, resultFormat string) *DimExtractionFn { - return &DimExtractionFn{ - Type: "time", - TimeFormat: timeFormat, - ResultFormat: resultFormat, - } -} - -func DimExFnJavascript(function string) *DimExtractionFn { - return &DimExtractionFn{ - Type: "javascript", - Function: function, - } -} diff --git a/pkg/query-service/godruid/filters.go b/pkg/query-service/godruid/filters.go deleted file mode 100644 index 9d71e724da..0000000000 --- a/pkg/query-service/godruid/filters.go +++ /dev/null @@ -1,110 +0,0 @@ -package godruid - -type FilterQueryType struct { - Type string `json:"type"` - Value interface{} `json:"value"` -} - -type Filter struct { - Type string `json:"type"` - Dimension string `json:"dimension,omitempty"` - Value interface{} `json:"value,omitempty"` - Pattern string `json:"pattern,omitempty"` - Function string `json:"function,omitempty"` - Field *Filter `json:"field,omitempty"` - Fields []*Filter `json:"fields,omitempty"` - Lower string `json:"lower,omitempty"` - LowerStrict bool `json:"lowerStrict,omitempty"` - Upper string `json:"upper,omitempty"` - UpperStrict bool `json:"upperStrict,omitempty"` - Ordering string `json:"ordering,omitempty"` - Query FilterQueryType `json:"query,omitempty"` -} - -func FilterSelector(dimension string, value interface{}) *Filter { - return &Filter{ - Type: "selector", - Dimension: dimension, - Value: value, - } -} - -func FilterBound(dimension string, lower string, upper string, lowerStrict bool, upperStrict bool, ordering string) *Filter { - - return &Filter{ - Type: "bound", - Dimension: dimension, - Lower: lower, - LowerStrict: lowerStrict, - Upper: upper, - UpperStrict: upperStrict, - Ordering: ordering, - } -} - -func FilterRegex(dimension, pattern string) *Filter { - return &Filter{ - Type: "regex", - Dimension: dimension, - Pattern: pattern, - } -} - -func FilterSearch(dimension string, value interface{}) *Filter { - return &Filter{ - Type: "search", - Dimension: dimension, - Query: FilterQueryType{ - Type: "insensitive_contains", - Value: value, - }, - } -} - -func FilterJavaScript(dimension, function string) *Filter { - return &Filter{ - Type: "javascript", - Dimension: dimension, - Function: function, - } -} - -func FilterAnd(filters ...*Filter) *Filter { - return joinFilters(filters, "and") -} - -func FilterOr(filters ...*Filter) *Filter { - return joinFilters(filters, "or") -} - -func FilterNot(filter *Filter) *Filter { - return &Filter{ - Type: "not", - Field: filter, - } -} - -func joinFilters(filters []*Filter, connector string) *Filter { - // Remove null filters. - p := 0 - for _, f := range filters { - if f != nil { - filters[p] = f - p++ - } - } - filters = filters[0:p] - - fLen := len(filters) - if fLen == 0 { - return nil - } - if fLen == 1 { - return filters[0] - } - - return &Filter{ - Type: connector, - Fields: filters, - } -} diff --git a/pkg/query-service/godruid/granularities.go b/pkg/query-service/godruid/granularities.go deleted file mode 100644 index 0861a08e1a..0000000000 --- a/pkg/query-service/godruid/granularities.go +++ /dev/null @@ -1,30 +0,0 @@ -package godruid - -type Granlarity interface{} - -type SimpleGran string - -const ( - GranAll SimpleGran = "all" - GranNone SimpleGran = "none" - GranMinute SimpleGran = "minute" - GranFifteenMin SimpleGran = "fifteen_minute" - GranThirtyMin SimpleGran = "thirty_minute" - GranHour SimpleGran = "hour" - GranDay SimpleGran = "day" -) - -type GranDuration struct { - Type string `json:"type"` - - Duration string `json:"duration"` - Origin string `json:"origin,omitempty"` -} - -type GranPeriod struct { - Type string `json:"type"` - - Period string `json:"period"` - TimeZone string `json:"timeZone,omitempty"` - Origin string `json:"origin,omitempty"` -} diff --git a/pkg/query-service/godruid/havings.go b/pkg/query-service/godruid/havings.go deleted file mode 100644 index cc04c0c29e..0000000000 --- a/pkg/query-service/godruid/havings.go +++ /dev/null @@ -1,73 +0,0 @@ -package godruid - -type Having struct { - Type string `json:"type"` - Aggregation string `json:"aggregation,omitempty"` - Value interface{} `json:"value,omitempty"` - HavingSpec *Having `json:"havingSpec,omitempty"` - HavingSpecs []*Having `json:"havingSpecs,omitempty"` -} - -func HavingEqualTo(agg string, value interface{}) *Having { - return &Having{ - Type: "equalTo", - Aggregation: agg, - Value: value, - } -} - -func HavingGreaterThan(agg string, value interface{}) *Having { - return &Having{ - Type: "greaterThan", - Aggregation: agg, - Value: value, - } -} - -func HavingLessThan(agg string, value interface{}) *Having { - return &Having{ - Type: "lessThan", - Aggregation: agg, - Value: value, - } -} - -func HavingAnd(havings ...*Having) *Having { - return joinHavings(havings, "and") -} - -func HavingOr(havings ...*Having) *Having { - return joinHavings(havings, "or") -} - -func HavingNot(having *Having) *Having { - return &Having{ - Type: "not", - HavingSpec: having, - } -} - -func joinHavings(havings []*Having, connector string) *Having { - // Remove null havings. - p := 0 - for _, h := range havings { - if h != nil { - havings[p] = h - p++ - } - } - havings = havings[0:p] - - fLen := len(havings) - if fLen == 0 { - return nil - } - if fLen == 1 { - return havings[0] - } - - return &Having{ - Type: connector, - HavingSpecs: havings, - } -} diff --git a/pkg/query-service/godruid/post_aggregations.go b/pkg/query-service/godruid/post_aggregations.go deleted file mode 100644 index a2b1b22890..0000000000 --- a/pkg/query-service/godruid/post_aggregations.go +++ /dev/null @@ -1,110 +0,0 @@ -package godruid - -import ( - "encoding/json" -) - -type PostAggregation struct { - Type string `json:"type"` - Name string `json:"name,omitempty"` - Value interface{} `json:"value,omitempty"` - Fn string `json:"fn,omitempty"` - Field PostAggregationField `json:"field,omitempty"` - Fields []PostAggregation `json:"fields,omitempty"` - FieldName string `json:"fieldName,omitempty"` - FieldNames []string `json:"fieldNames,omitempty"` - Function string `json:"function,omitempty"` - Fraction float32 `json:"fraction,omitempty"` - Fractions []float32 `json:"fractions,omitempty"` -} - -type PostAggregationField struct { - Type string `json:"type,omitempty"` - FieldName string `json:"fieldName,omitempty"` -} - -func NewPostAggregationField(fieldName string) PostAggregationField { - return PostAggregationField{ - Type: "fieldAccess", - FieldName: fieldName, - } -} - -// The agg reference. -type AggRefer struct { - Name string - Refer string // The refer of Name, empty means Name has no refer. -} - -// Return the aggregations or post aggregations which this post aggregation used. -// It could be helpful while automatically filling the aggregations or post aggregations base on this. -func (pa PostAggregation) GetReferAggs(parentName ...string) (refers []AggRefer) { - switch pa.Type { - case "arithmetic": - if len(parentName) != 0 { - refers = append(refers, AggRefer{parentName[0], pa.Name}) - } else { - refers = append(refers, AggRefer{pa.Name, ""}) - } - for _, spa := range pa.Fields { - refers = append(refers, spa.GetReferAggs(pa.Name)...) - } - case "fieldAccess": - refers = append(refers, AggRefer{parentName[0], pa.FieldName}) - case "constant": - // no need refers. - case "javascript": - for _, f := range pa.FieldNames { - refers = append(refers, AggRefer{pa.Name, f}) - } - case "hyperUniqueCardinality": - refers = append(refers, AggRefer{parentName[0], pa.FieldName}) - } - return -} - -func PostAggRawJson(rawJson string) PostAggregation { - pa := &PostAggregation{} - json.Unmarshal([]byte(rawJson), pa) - return *pa -} - -func PostAggArithmetic(name, fn string, fields []PostAggregation) PostAggregation { - return PostAggregation{ - Type: "arithmetic", - Name: name, - Fn: fn, - Fields: fields, - } -} - -func PostAggFieldAccessor(fieldName string) PostAggregation { - return PostAggregation{ - Type: "fieldAccess", - FieldName: fieldName, - } -} - -func PostAggConstant(name string, value interface{}) PostAggregation { - return PostAggregation{ - Type: "constant", - Name: name, - Value: value, - } -} - -func PostAggJavaScript(name, function string, fieldNames []string) PostAggregation { - return PostAggregation{ - Type: "javascript", - Name: name, - FieldNames: fieldNames, - Function: function, - } -} - -func PostAggFieldHyperUnique(fieldName string) PostAggregation { - return PostAggregation{ - Type: "hyperUniqueCardinality", - FieldName: fieldName, - } -} diff --git a/pkg/query-service/godruid/queries.go b/pkg/query-service/godruid/queries.go deleted file mode 100644 index ffdeee002d..0000000000 --- a/pkg/query-service/godruid/queries.go +++ /dev/null @@ -1,324 +0,0 @@ -package godruid - -import ( - "encoding/json" -) - -// Check http://druid.io/docs/0.6.154/Querying.html#query-operators for detail description. - -// The Query interface stands for any kinds of druid query. -type Query interface { - setup() - onResponse(content []byte) error -} - -// --------------------------------- -// GroupBy Query -// --------------------------------- - -type QueryGroupBy struct { - QueryType string `json:"queryType"` - DataSource string `json:"dataSource"` - Dimensions []DimSpec `json:"dimensions"` - Granularity Granlarity `json:"granularity"` - LimitSpec *Limit `json:"limitSpec,omitempty"` - Having *Having `json:"having,omitempty"` - Filter *Filter `json:"filter,omitempty"` - Aggregations []Aggregation `json:"aggregations"` - PostAggregations []PostAggregation `json:"postAggregations,omitempty"` - Intervals []string `json:"intervals"` - Context map[string]interface{} `json:"context,omitempty"` - - QueryResult []GroupbyItem `json:"-"` -} - -type GroupbyItem struct { - Version string `json:"version"` - Timestamp string `json:"timestamp"` - Event map[string]interface{} `json:"event"` -} - -func (q *QueryGroupBy) setup() { q.QueryType = "groupBy" } -func (q *QueryGroupBy) onResponse(content []byte) error { - res := new([]GroupbyItem) - err := json.Unmarshal(content, res) - if err != nil { - return err - } - q.QueryResult = *res - return nil -} - -// --------------------------------- -// Scan Query -// --------------------------------- - -type QueryScan struct { - QueryType string `json:"queryType"` - DataSource string `json:"dataSource"` - Filter *Filter `json:"filter,omitempty"` - Intervals []string `json:"intervals"` - Columns []string `json:"columns,omitempty"` - Limit int64 `json:"limit,omitempty"` - Offset int64 `json:"offset,omitempty"` - BatchSize int64 `json:"batchSize,omitempty"` - Order string `json:"order,omitempty"` - ResultFormat string `json:"resultFormat"` - Context map[string]interface{} `json:"context,omitempty"` - - QueryResult []ScanResult `json:"-"` -} - -type ScanResult struct { - SegmentId string `json:"segmentId"` - Columns []string `json:"columns"` - Events [][]interface{} `json:"events"` -} - -func (q *QueryScan) setup() { - q.QueryType = "scan" - q.ResultFormat = "compactedList" - // q.BatchSize = 20480 - // q.Limit = 10 -} -func (q *QueryScan) onResponse(content []byte) error { - // fmt.Println(string(content)) - res := new([]ScanResult) - err := json.Unmarshal(content, res) - if err != nil { - return err - } - q.QueryResult = *res - return nil -} - -// --------------------------------- -// Search Query -// --------------------------------- - -type QuerySearch struct { - QueryType string `json:"queryType"` - DataSource string `json:"dataSource"` - Granularity Granlarity `json:"granularity"` - Filter *Filter `json:"filter,omitempty"` - Intervals []string `json:"intervals"` - SearchDimensions []string `json:"searchDimensions,omitempty"` - Query *SearchQuery `json:"query"` - Sort *SearchSort `json:"sort"` - Context map[string]interface{} `json:"context,omitempty"` - - QueryResult []SearchItem `json:"-"` -} - -type SearchItem struct { - Timestamp string `json:"timestamp"` - Result []DimValue `json:"result"` -} - -type DimValue struct { - Dimension string `json:"dimension"` - Value string `json:"value"` -} - -func (q *QuerySearch) setup() { q.QueryType = "search" } -func (q *QuerySearch) onResponse(content []byte) error { - res := new([]SearchItem) - err := json.Unmarshal(content, res) - if err != nil { - return err - } - q.QueryResult = *res - return nil -} - -// --------------------------------- -// SegmentMetadata Query -// --------------------------------- - -type QuerySegmentMetadata struct { - QueryType string `json:"queryType"` - DataSource string `json:"dataSource"` - Intervals []string `json:"intervals"` - ToInclude *ToInclude `json:"toInclude,omitempty"` - Merge interface{} `json:"merge,omitempty"` - Context map[string]interface{} `json:"context,omitempty"` - - QueryResult []SegmentMetaData `json:"-"` -} - -type SegmentMetaData struct { - Id string `json:"id"` - Intervals []string `json:"intervals"` - Columns map[string]ColumnItem `json:"columns"` -} - -type ColumnItem struct { - Type string `json:"type"` - Size int `json:"size"` - Cardinality interface{} `json:"cardinality"` -} - -func (q *QuerySegmentMetadata) setup() { q.QueryType = "segmentMetadata" } -func (q *QuerySegmentMetadata) onResponse(content []byte) error { - res := new([]SegmentMetaData) - err := json.Unmarshal(content, res) - if err != nil { - return err - } - q.QueryResult = *res - return nil -} - -// --------------------------------- -// TimeBoundary Query -// --------------------------------- - -type QueryTimeBoundary struct { - QueryType string `json:"queryType"` - DataSource string `json:"dataSource"` - Bound string `json:"bound,omitempty"` - Context map[string]interface{} `json:"context,omitempty"` - - QueryResult []TimeBoundaryItem `json:"-"` -} - -type TimeBoundaryItem struct { - Timestamp string `json:"timestamp"` - Result TimeBoundary `json:"result"` -} - -type TimeBoundary struct { - MinTime string `json:"minTime"` - MaxTime string `json:"maxTime"` -} - -func (q *QueryTimeBoundary) setup() { q.QueryType = "timeBoundary" } -func (q *QueryTimeBoundary) onResponse(content []byte) error { - res := new([]TimeBoundaryItem) - err := json.Unmarshal(content, res) - if err != nil { - return err - } - q.QueryResult = *res - return nil -} - -// --------------------------------- -// Timeseries Query -// --------------------------------- - -type QueryTimeseries struct { - QueryType string `json:"queryType"` - DataSource string `json:"dataSource"` - Granularity Granlarity `json:"granularity"` - Filter *Filter `json:"filter,omitempty"` - Aggregations []Aggregation `json:"aggregations"` - PostAggregations []PostAggregation `json:"postAggregations,omitempty"` - Intervals []string `json:"intervals"` - Context map[string]interface{} `json:"context,omitempty"` - - QueryResult []Timeseries `json:"-"` -} - -type Timeseries struct { - Timestamp string `json:"timestamp"` - Result map[string]interface{} `json:"result"` -} - -func (q *QueryTimeseries) setup() { q.QueryType = "timeseries" } -func (q *QueryTimeseries) onResponse(content []byte) error { - res := new([]Timeseries) - err := json.Unmarshal(content, res) - if err != nil { - return err - } - q.QueryResult = *res - return nil -} - -// --------------------------------- -// TopN Query -// --------------------------------- - -type QueryTopN struct { - QueryType string `json:"queryType"` - DataSource string `json:"dataSource"` - Granularity Granlarity `json:"granularity"` - Dimension DimSpec `json:"dimension"` - Threshold int `json:"threshold"` - Metric *TopNMetric `json:"metric"` - Filter *Filter `json:"filter,omitempty"` - Aggregations []Aggregation `json:"aggregations"` - PostAggregations []PostAggregation `json:"postAggregations,omitempty"` - Intervals []string `json:"intervals"` - Context map[string]interface{} `json:"context,omitempty"` - - QueryResult []TopNItem `json:"-"` -} - -type TopNItem struct { - Timestamp string `json:"timestamp"` - Result []map[string]interface{} `json:"result"` -} - -func (q *QueryTopN) setup() { q.QueryType = "topN" } -func (q *QueryTopN) onResponse(content []byte) error { - res := new([]TopNItem) - err := json.Unmarshal(content, res) - if err != nil { - return err - } - q.QueryResult = *res - return nil -} - -// --------------------------------- -// Select Query -// --------------------------------- - -type QuerySelect struct { - QueryType string `json:"queryType"` - DataSource string `json:"dataSource"` - Intervals []string `json:"intervals"` - Filter *Filter `json:"filter,omitempty"` - Dimensions []DimSpec `json:"dimensions"` - Metrics []string `json:"metrics"` - Granularity Granlarity `json:"granularity"` - PagingSpec map[string]interface{} `json:"pagingSpec,omitempty"` - Context map[string]interface{} `json:"context,omitempty"` - - QueryResult SelectBlob `json:"-"` -} - -// Select json blob from druid comes back as following: -// http://druid.io/docs/latest/querying/select-query.html -// the interesting results are in events blob which we -// call as 'SelectEvent'. -type SelectBlob struct { - Timestamp string `json:"timestamp"` - Result SelectResult `json:"result"` -} - -type SelectResult struct { - PagingIdentifiers map[string]interface{} `json:"pagingIdentifiers"` - Events []SelectEvent `json:"events"` -} - -type SelectEvent struct { - SegmentId string `json:"segmentId"` - Offset int64 `json:"offset"` - Event map[string]interface{} `json:"event"` -} - -func (q *QuerySelect) setup() { q.QueryType = "select" } -func (q *QuerySelect) onResponse(content []byte) error { - res := new([]SelectBlob) - err := json.Unmarshal(content, res) - if err != nil { - return err - } - if len(*res) > 0 { - q.QueryResult = (*res)[0] - } - return nil -} diff --git a/pkg/query-service/godruid/specs.go b/pkg/query-service/godruid/specs.go deleted file mode 100644 index feab638507..0000000000 --- a/pkg/query-service/godruid/specs.go +++ /dev/null @@ -1,132 +0,0 @@ -package godruid - -// Defines some small spec like structs here. - -// --------------------------------- -// LimitSpec -// --------------------------------- - -type Limit struct { - Type string `json:"type"` - Limit int `json:"limit"` - Columns []Column `json:"columns,omitempty"` -} - -const ( - DirectionASC = "ASCENDING" - DirectionDESC = "DESCENDING" -) - -type Column struct { - AsNumber bool `json:"asNumber"` - Dimension string `json:"dimension"` - Direction string `json:"direction"` -} - -func LimitDefault(limit int, columns ...[]Column) *Limit { - var realColums []Column - if len(columns) > 0 { - realColums = columns[0] - } - return &Limit{ - Type: "default", - Limit: limit, - Columns: realColums, - } -} - -// --------------------------------- -// SearchQuerySpec -// --------------------------------- - -type SearchQuery struct { - Type string `json:"type"` - Value interface{} `json:"value,omitempty"` - Values []interface{} `json:"values,omitempty"` -} - -func SearchQueryInsensitiveContains(value interface{}) *SearchQuery { - return &SearchQuery{ - Type: "insensitive_contains", - Value: value, - } -} - -func SearchQueryFragmentSearch(values []interface{}) *SearchQuery { - return &SearchQuery{ - Type: "fragment", - Values: values, - } -} - -// --------------------------------- -// ToInclude -// --------------------------------- - -type ToInclude struct { - Type string `json:"type"` - Columns []string `json:"columns,omitempty"` -} - -var ( - ToIncludeAll = &ToInclude{Type: "All"} - ToIncludeNone = &ToInclude{Type: "None"} -) - -func ToIncludeList(columns []string) *ToInclude { - return &ToInclude{ - Type: "list", - Columns: columns, - } -} - -// --------------------------------- -// TopNMetricSpec -// --------------------------------- - -type TopNMetric struct { - Type string `json:"type"` - Metric interface{} `json:"metric,omitempty"` - PreviousStop string `json:"previousStop"` -} - -func TopNMetricNumeric(metric string) *TopNMetric { - return &TopNMetric{ - Type: "numeric", - Metric: metric, - } -} - -func TopNMetricLexicographic(previousStop string) *TopNMetric { - return &TopNMetric{ - Type: "lexicographic", - PreviousStop: previousStop, - } -} - -func TopNMetricAlphaNumeric(previousStop string) *TopNMetric { - return &TopNMetric{ - Type: "alphaNumeric", - PreviousStop: previousStop, - } -} - -func TopNMetricInverted(metric *TopNMetric) *TopNMetric { - return &TopNMetric{ - Type: "inverted", - Metric: metric, - } -} - -// --------------------------------- -// SearchSortSpec -// --------------------------------- - -type SearchSort struct { - Type string `json:"type"` -} - -var ( - SearchSortLexicographic = &SearchSort{Type: "lexicographic"} - SearchSortStrlen = &SearchSort{Type: "strlen"} -) diff --git a/pkg/query-service/integrations/alertManager/manager.go b/pkg/query-service/integrations/alertManager/manager.go index f0e8b024d1..47dc96f366 100644 --- a/pkg/query-service/integrations/alertManager/manager.go +++ b/pkg/query-service/integrations/alertManager/manager.go @@ -2,13 +2,14 @@ package alertManager // Wrapper to connect and process alert manager functions import ( - "fmt" - "encoding/json" "bytes" + "encoding/json" + "fmt" "net/http" - "go.uber.org/zap" + "go.signoz.io/query-service/constants" "go.signoz.io/query-service/model" + "go.uber.org/zap" ) const contentType = "application/json" @@ -17,16 +18,17 @@ type Manager interface { AddRoute(receiver *Receiver) *model.ApiError EditRoute(receiver *Receiver) *model.ApiError DeleteRoute(name string) *model.ApiError + TestReceiver(receiver *Receiver) *model.ApiError } -func New(url string) Manager{ - - if url == ""{ +func New(url string) Manager { + + if url == "" { url = constants.GetAlertManagerApiPrefix() } - return &manager { - url: url, + return &manager{ + url: url, } } @@ -34,11 +36,10 @@ type manager struct { url string } - func prepareAmChannelApiURL() string { basePath := constants.GetAlertManagerApiPrefix() AmChannelApiPath := constants.AmChannelApiPath - + if len(AmChannelApiPath) > 0 && rune(AmChannelApiPath[0]) == rune('/') { AmChannelApiPath = AmChannelApiPath[1:] } @@ -46,13 +47,18 @@ func prepareAmChannelApiURL() string { return fmt.Sprintf("%s%s", basePath, AmChannelApiPath) } -func (m *manager) AddRoute(receiver *Receiver) (*model.ApiError) { - +func prepareTestApiURL() string { + basePath := constants.GetAlertManagerApiPrefix() + return fmt.Sprintf("%s%s", basePath, "v1/testReceiver") +} + +func (m *manager) AddRoute(receiver *Receiver) *model.ApiError { + receiverString, _ := json.Marshal(receiver) amURL := prepareAmChannelApiURL() response, err := http.Post(amURL, contentType, bytes.NewBuffer(receiverString)) - + if err != nil { zap.S().Errorf(fmt.Sprintf("Error in getting response of API call to alertmanager(POST %s)\n", amURL), err) return &model.ApiError{Typ: model.ErrorInternal, Err: err} @@ -81,7 +87,7 @@ func (m *manager) EditRoute(receiver *Receiver) *model.ApiError { client := &http.Client{} response, err := client.Do(req) - + if err != nil { zap.S().Errorf(fmt.Sprintf("Error in getting response of API call to alertmanager(PUT %s)\n", amURL), err) return &model.ApiError{Typ: model.ErrorInternal, Err: err} @@ -125,5 +131,29 @@ func (m *manager) DeleteRoute(name string) *model.ApiError { return nil } +func (m *manager) TestReceiver(receiver *Receiver) *model.ApiError { + receiverBytes, _ := json.Marshal(receiver) + amTestURL := prepareTestApiURL() + response, err := http.Post(amTestURL, contentType, bytes.NewBuffer(receiverBytes)) + + if err != nil { + zap.S().Errorf(fmt.Sprintf("Error in getting response of API call to alertmanager(POST %s)\n", amTestURL), err) + return &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + + if response.StatusCode > 201 && response.StatusCode < 400 { + err := fmt.Errorf(fmt.Sprintf("Invalid parameters in test alert api for alertmanager(POST %s)\n", amTestURL), response.Status) + zap.S().Error(err) + return &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + + if response.StatusCode > 400 { + err := fmt.Errorf(fmt.Sprintf("Received Server Error response for API call to alertmanager(POST %s)\n", amTestURL), response.Status) + zap.S().Error(err) + return &model.ApiError{Typ: model.ErrorInternal, Err: err} + } + + return nil +} diff --git a/pkg/query-service/main.go b/pkg/query-service/main.go index 3418154d79..9cede29cd7 100644 --- a/pkg/query-service/main.go +++ b/pkg/query-service/main.go @@ -1,14 +1,13 @@ package main import ( + "context" "os" "os/signal" "syscall" - _ "github.com/golang-migrate/migrate/v4/database/postgres" - _ "github.com/golang-migrate/migrate/v4/source/github" - "go.signoz.io/query-service/app" + "go.signoz.io/query-service/auth" "go.signoz.io/query-service/constants" "go.signoz.io/query-service/version" @@ -42,6 +41,15 @@ func main() { // DruidClientUrl: constants.DruidClientUrl, } + // Read the jwt secret key + auth.JwtSecret = os.Getenv("SIGNOZ_JWT_SECRET") + + if len(auth.JwtSecret) == 0 { + zap.S().Warn("No JWT secret key is specified.") + } else { + zap.S().Info("No JWT secret key set successfully.") + } + server, err := app.NewServer(serverOptions) if err != nil { logger.Fatal("Failed to create server", zap.Error(err)) @@ -51,6 +59,10 @@ func main() { logger.Fatal("Could not start servers", zap.Error(err)) } + if err := auth.InitAuthCache(context.Background()); err != nil { + logger.Fatal("Failed to initialize auth cache", zap.Error(err)) + } + signalsChannel := make(chan os.Signal, 1) signal.Notify(signalsChannel, os.Interrupt, syscall.SIGTERM) diff --git a/pkg/query-service/model/auth.go b/pkg/query-service/model/auth.go new file mode 100644 index 0000000000..e5d3e7bfbf --- /dev/null +++ b/pkg/query-service/model/auth.go @@ -0,0 +1,57 @@ +package model + +import "github.com/pkg/errors" + +var ( + ErrorTokenExpired = errors.New("Token is expired") +) + +type InviteRequest struct { + Name string `json:"name"` + Email string `json:"email"` + Role string `json:"role"` +} + +type InviteResponse struct { + Email string `json:"email"` + InviteToken string `json:"inviteToken"` +} + +type InvitationResponseObject struct { + Email string `json:"email" db:"email"` + Name string `json:"name" db:"name"` + Token string `json:"token" db:"token"` + CreatedAt int64 `json:"createdAt" db:"created_at"` + Role string `json:"role" db:"role"` + Organization string `json:"organization" db:"organization"` +} + +type LoginRequest struct { + Email string `json:"email"` + Password string `json:"password"` + RefreshToken string `json:"refreshToken"` +} + +type LoginResponse struct { + AccessJwt string `json:"accessJwt"` + AccessJwtExpiry int64 `json:"accessJwtExpiry"` + RefreshJwt string `json:"refreshJwt"` + RefreshJwtExpiry int64 `json:"refreshJwtExpiry"` + UserId string `json:"userId"` +} + +type ChangePasswordRequest struct { + UserId string `json:"userId"` + OldPassword string `json:"oldPassword"` + NewPassword string `json:"newPassword"` +} + +type ResetPasswordEntry struct { + UserId string `json:"userId" db:"user_id"` + Token string `json:"token" db:"token"` +} + +type UserRole struct { + UserId string `json:"user_id"` + GroupName string `json:"group_name"` +} diff --git a/pkg/query-service/model/db.go b/pkg/query-service/model/db.go new file mode 100644 index 0000000000..0c198fbf28 --- /dev/null +++ b/pkg/query-service/model/db.go @@ -0,0 +1,46 @@ +package model + +type Organization struct { + Id string `json:"id" db:"id"` + Name string `json:"name" db:"name"` + CreatedAt int64 `json:"createdAt" db:"created_at"` + IsAnonymous bool `json:"isAnonymous" db:"is_anonymous"` + HasOptedUpdates bool `json:"hasOptedUpdates" db:"has_opted_updates"` +} + +type InvitationObject struct { + Id string `json:"id" db:"id"` + Email string `json:"email" db:"email"` + Name string `json:"name" db:"name"` + Token string `json:"token" db:"token"` + CreatedAt int64 `json:"createdAt" db:"created_at"` + Role string `json:"role" db:"role"` + OrgId string `json:"orgId" db:"org_id"` +} + +type User struct { + Id string `json:"id" db:"id"` + Name string `json:"name" db:"name"` + Email string `json:"email" db:"email"` + Password string `json:"password,omitempty" db:"password"` + CreatedAt int64 `json:"createdAt" db:"created_at"` + ProfilePirctureURL string `json:"profilePictureURL" db:"profile_picture_url"` + OrgId string `json:"orgId,omitempty" db:"org_id"` + GroupId string `json:"groupId,omitempty" db:"group_id"` +} + +type UserPayload struct { + User + Role string `json:"role"` + Organization string `json:"organization"` +} + +type Group struct { + Id string `json:"id" db:"id"` + Name string `json:"name" db:"name"` +} + +type ResetPasswordRequest struct { + Password string `json:"password"` + Token string `json:"token"` +} diff --git a/pkg/query-service/model/queryParams.go b/pkg/query-service/model/queryParams.go index 938ce02151..e93989de94 100644 --- a/pkg/query-service/model/queryParams.go +++ b/pkg/query-service/model/queryParams.go @@ -1,16 +1,9 @@ package model import ( - "fmt" "time" ) -type User struct { - Name string `json:"name"` - Email string `json:"email"` - OrganizationName string `json:"organizationName"` -} - type InstantQueryMetricsParams struct { Time time.Time Query string @@ -25,12 +18,43 @@ type QueryRangeParams struct { Stats string } +type Query struct { + Datasource string `json:"datasource"` + Format string `json:"format"` + Expr string `json:"expr"` +} + +type QueryRangeParamsV2 struct { + Start time.Time + End time.Time + Step time.Duration + StartStr string `json:"start"` + EndStr string `json:"end"` + StepStr string `json:"step"` + Queries []Query `json:"queries"` +} + +func (params QueryRangeParamsV2) sanitizeAndValidate() (*QueryRangeParamsV2, error) { + + return nil, nil +} + +type metricTags map[string]string + +type MetricAutocompleteTagParams struct { + MetricName string + MetricTags metricTags + Match string + TagKey string +} + type GetTopEndpointsParams struct { - StartTime string - EndTime string - ServiceName string + StartTime string `json:"start"` + EndTime string `json:"end"` + ServiceName string `json:"service"` Start *time.Time End *time.Time + Tags []TagQuery `json:"tags"` } type GetUsageParams struct { @@ -44,125 +68,78 @@ type GetUsageParams struct { } type GetServicesParams struct { - StartTime string - EndTime string + StartTime string `json:"start"` + EndTime string `json:"end"` Period int Start *time.Time End *time.Time + Tags []TagQuery `json:"tags"` } type GetServiceOverviewParams struct { - StartTime string - EndTime string + StartTime string `json:"start"` + EndTime string `json:"end"` + Period string Start *time.Time End *time.Time - ServiceName string - Period string - StepSeconds int -} - -type ApplicationPercentileParams struct { - ServiceName string - GranOrigin string - GranPeriod string - Intervals string -} - -func (query *ApplicationPercentileParams) SetGranPeriod(step int) { - minutes := step / 60 - query.GranPeriod = fmt.Sprintf("PT%dM", minutes) + Tags []TagQuery `json:"tags"` + ServiceName string `json:"service"` + StepSeconds int `json:"step"` } type TagQuery struct { - Key string - Value string - Operator string -} - -type TagQueryV2 struct { Key string Values []string Operator string } -type SpanSearchAggregatesParams struct { - ServiceName string - OperationName string - Kind string - MinDuration string - MaxDuration string - Tags []TagQuery - Start *time.Time - End *time.Time - GranOrigin string - GranPeriod string - Intervals string - StepSeconds int - Dimension string - AggregationOption string -} - -type SpanSearchParams struct { - ServiceName string - OperationName string - Kind string - Intervals string - Start *time.Time - End *time.Time - MinDuration string - MaxDuration string - Limit int64 - Order string - Offset int64 - BatchSize int64 - Tags []TagQuery -} type GetFilteredSpansParams struct { - ServiceName []string `json:"serviceName"` - Operation []string `json:"operation"` - Kind string `json:"kind"` - Status []string `json:"status"` - HttpRoute []string `json:"httpRoute"` - HttpCode []string `json:"httpCode"` - HttpUrl []string `json:"httpUrl"` - HttpHost []string `json:"httpHost"` - HttpMethod []string `json:"httpMethod"` - Component []string `json:"component"` - StartStr string `json:"start"` - EndStr string `json:"end"` - MinDuration string `json:"minDuration"` - MaxDuration string `json:"maxDuration"` - Limit int64 `json:"limit"` - Order string `json:"order"` - Offset int64 `json:"offset"` - Tags []TagQueryV2 `json:"tags"` - Exclude []string `json:"exclude"` + ServiceName []string `json:"serviceName"` + Operation []string `json:"operation"` + Kind string `json:"kind"` + Status []string `json:"status"` + HttpRoute []string `json:"httpRoute"` + HttpCode []string `json:"httpCode"` + HttpUrl []string `json:"httpUrl"` + HttpHost []string `json:"httpHost"` + HttpMethod []string `json:"httpMethod"` + Component []string `json:"component"` + StartStr string `json:"start"` + EndStr string `json:"end"` + MinDuration string `json:"minDuration"` + MaxDuration string `json:"maxDuration"` + Limit int64 `json:"limit"` + OrderParam string `json:"orderParam"` + Order string `json:"order"` + Offset int64 `json:"offset"` + Tags []TagQuery `json:"tags"` + Exclude []string `json:"exclude"` Start *time.Time End *time.Time } type GetFilteredSpanAggregatesParams struct { - ServiceName []string `json:"serviceName"` - Operation []string `json:"operation"` - Kind string `json:"kind"` - Status []string `json:"status"` - HttpRoute []string `json:"httpRoute"` - HttpCode []string `json:"httpCode"` - HttpUrl []string `json:"httpUrl"` - HttpHost []string `json:"httpHost"` - HttpMethod []string `json:"httpMethod"` - Component []string `json:"component"` - MinDuration string `json:"minDuration"` - MaxDuration string `json:"maxDuration"` - Tags []TagQueryV2 `json:"tags"` - StartStr string `json:"start"` - EndStr string `json:"end"` - StepSeconds int `json:"step"` - Dimension string `json:"dimension"` - AggregationOption string `json:"aggregationOption"` - GroupBy string `json:"groupBy"` - Function string `json:"function"` - Exclude []string `json:"exclude"` + ServiceName []string `json:"serviceName"` + Operation []string `json:"operation"` + Kind string `json:"kind"` + Status []string `json:"status"` + HttpRoute []string `json:"httpRoute"` + HttpCode []string `json:"httpCode"` + HttpUrl []string `json:"httpUrl"` + HttpHost []string `json:"httpHost"` + HttpMethod []string `json:"httpMethod"` + Component []string `json:"component"` + MinDuration string `json:"minDuration"` + MaxDuration string `json:"maxDuration"` + Tags []TagQuery `json:"tags"` + StartStr string `json:"start"` + EndStr string `json:"end"` + StepSeconds int `json:"step"` + Dimension string `json:"dimension"` + AggregationOption string `json:"aggregationOption"` + GroupBy string `json:"groupBy"` + Function string `json:"function"` + Exclude []string `json:"exclude"` Start *time.Time End *time.Time } @@ -208,10 +185,10 @@ type TagFilterParams struct { } type TTLParams struct { - Type string // It can be one of {traces, metrics}. - ColdStorageVolume string // Name of the cold storage volume. - ToColdStorageDuration float64 // Seconds after which data will be moved to cold storage. - DelDuration float64 // Seconds after which data will be deleted. + Type string // It can be one of {traces, metrics}. + ColdStorageVolume string // Name of the cold storage volume. + ToColdStorageDuration int64 // Seconds after which data will be moved to cold storage. + DelDuration int64 // Seconds after which data will be deleted. } type GetTTLParams struct { diff --git a/pkg/query-service/model/response.go b/pkg/query-service/model/response.go index 8d4bd4b766..959e2b23d4 100644 --- a/pkg/query-service/model/response.go +++ b/pkg/query-service/model/response.go @@ -1,15 +1,14 @@ package model import ( - "database/sql" "encoding/json" "fmt" "strconv" "time" - "github.com/prometheus/prometheus/pkg/labels" "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/util/stats" + "k8s.io/apimachinery/pkg/labels" ) type ApiError struct { @@ -28,8 +27,15 @@ const ( ErrorUnavailable ErrorType = "unavailable" ErrorNotFound ErrorType = "not_found" ErrorNotImplemented ErrorType = "not_implemented" + ErrorUnauthorized ErrorType = "unauthorized" + ErrorForbidden ErrorType = "forbidden" ) +type QueryDataV2 struct { + ResultType promql.ValueType `json:"resultType"` + Result promql.Value `json:"result"` +} + type QueryData struct { ResultType promql.ValueType `json:"resultType"` Result promql.Value `json:"result"` @@ -68,39 +74,31 @@ type AlertingRuleResponse struct { } type ServiceItem struct { - ServiceName string `json:"serviceName" db:"serviceName"` - Percentile99 float32 `json:"p99" db:"p99"` - AvgDuration float32 `json:"avgDuration" db:"avgDuration"` - NumCalls int `json:"numCalls" db:"numCalls"` - CallRate float32 `json:"callRate" db:"callRate"` - NumErrors int `json:"numErrors" db:"numErrors"` - ErrorRate float32 `json:"errorRate" db:"errorRate"` - Num4XX int `json:"num4XX" db:"num4xx"` - FourXXRate float32 `json:"fourXXRate" db:"fourXXRate"` + ServiceName string `json:"serviceName" ch:"serviceName"` + Percentile99 float64 `json:"p99" ch:"p99"` + AvgDuration float64 `json:"avgDuration" ch:"avgDuration"` + NumCalls uint64 `json:"numCalls" ch:"numCalls"` + CallRate float64 `json:"callRate" ch:"callRate"` + NumErrors uint64 `json:"numErrors" ch:"numErrors"` + ErrorRate float64 `json:"errorRate" ch:"errorRate"` + Num4XX uint64 `json:"num4XX" ch:"num4xx"` + FourXXRate float64 `json:"fourXXRate" ch:"fourXXRate"` } - -type ServiceListErrorItem struct { - ServiceName string `json:"serviceName"` - NumErrors int `json:"numErrors"` - Num4xx int `json:"num4xx"` -} - type ServiceErrorItem struct { - Time string `json:"time,omitempty" db:"time,omitempty"` - Timestamp int64 `json:"timestamp" db:"timestamp"` - NumErrors int `json:"numErrors" db:"numErrors"` + Time time.Time `json:"time" ch:"time"` + Timestamp int64 `json:"timestamp" ch:"timestamp"` + NumErrors uint64 `json:"numErrors" ch:"numErrors"` } - type ServiceOverviewItem struct { - Time string `json:"time,omitempty" db:"time,omitempty"` - Timestamp int64 `json:"timestamp" db:"timestamp"` - Percentile50 float32 `json:"p50" db:"p50"` - Percentile95 float32 `json:"p95" db:"p95"` - Percentile99 float32 `json:"p99" db:"p99"` - NumCalls int `json:"numCalls" db:"numCalls"` - CallRate float32 `json:"callRate" db:"callRate"` - NumErrors int `json:"numErrors" db:"numErrors"` - ErrorRate float32 `json:"errorRate" db:"errorRate"` + Time time.Time `json:"time" ch:"time"` + Timestamp int64 `json:"timestamp" ch:"timestamp"` + Percentile50 float64 `json:"p50" ch:"p50"` + Percentile95 float64 `json:"p95" ch:"p95"` + Percentile99 float64 `json:"p99" ch:"p99"` + NumCalls uint64 `json:"numCalls" ch:"numCalls"` + CallRate float64 `json:"callRate" ch:"callRate"` + NumErrors uint64 `json:"numErrors" ch:"numErrors"` + ErrorRate float64 `json:"errorRate" ch:"errorRate"` } type SearchSpansResult struct { @@ -109,56 +107,50 @@ type SearchSpansResult struct { } type GetFilterSpansResponseItem struct { - Timestamp string `db:"timestamp" json:"timestamp"` - SpanID string `db:"spanID" json:"spanID"` - TraceID string `db:"traceID" json:"traceID"` - ServiceName string `db:"serviceName" json:"serviceName"` - Operation string `db:"name" json:"operation"` - DurationNano int64 `db:"durationNano" json:"durationNano"` - HttpCode string `db:"httpCode" json:"httpCode"` - HttpMethod string `db:"httpMethod" json:"httpMethod"` + Timestamp time.Time `ch:"timestamp" json:"timestamp"` + SpanID string `ch:"spanID" json:"spanID"` + TraceID string `ch:"traceID" json:"traceID"` + ServiceName string `ch:"serviceName" json:"serviceName"` + Operation string `ch:"name" json:"operation"` + DurationNano uint64 `ch:"durationNano" json:"durationNano"` + HttpCode string `ch:"httpCode"` + HttpMethod string `ch:"httpMethod"` + GRPCode string `ch:"gRPCCode"` + GRPMethod string `ch:"gRPCMethod"` + StatusCode string `json:"statusCode"` + Method string `json:"method"` } type GetFilterSpansResponse struct { Spans []GetFilterSpansResponseItem `json:"spans"` - TotalSpans int `json:"totalSpans"` + TotalSpans uint64 `json:"totalSpans"` } -type TraceResult struct { - Data []interface{} `json:"data" db:"data"` - Total int `json:"total" db:"total"` - Limit int `json:"limit" db:"limit"` - Offset int `json:"offset" db:"offset"` +type SearchSpanDBReponseItem struct { + Timestamp time.Time `ch:"timestamp"` + TraceID string `ch:"traceID"` + Model string `ch:"model"` } -type TraceResultItem struct { - TraceID string - Spans []TraceResultSpan -} -type TraceResultSpan struct { - Timestamp string `db:"timestamp"` - SpanID string `db:"spanID"` - TraceID string `db:"traceID"` - ServiceName string `db:"serviceName"` - Name string `db:"name"` - Kind int32 `db:"kind"` - DurationNano int64 `db:"durationNano"` - TagsKeys []string `db:"tagsKeys"` - TagsValues []string `db:"tagsValues"` + +type Event struct { + Name string `json:"name,omitempty"` + TimeUnixNano uint64 `json:"timeUnixNano,omitempty"` + AttributeMap map[string]interface{} `json:"attributeMap,omitempty"` + IsError bool `json:"isError,omitempty"` } type SearchSpanReponseItem struct { - Timestamp string `db:"timestamp"` - SpanID string `db:"spanID"` - TraceID string `db:"traceID"` - ServiceName string `db:"serviceName"` - Name string `db:"name"` - Kind int32 `db:"kind"` - References string `db:"references,omitempty"` - DurationNano int64 `db:"durationNano"` - TagsKeys []string `db:"tagsKeys"` - TagsValues []string `db:"tagsValues"` - Events []string `db:"events"` - HasError int32 `db:"hasError"` + TimeUnixNano uint64 `json:"timestamp"` + SpanID string `json:"spanID"` + TraceID string `json:"traceID"` + ServiceName string `json:"serviceName"` + Name string `json:"name"` + Kind int32 `json:"kind"` + References []OtelSpanRef `json:"references,omitempty"` + DurationNano int64 `json:"durationNano"` + TagMap map[string]string `json:"tagMap"` + Events []string `json:"event"` + HasError bool `json:"hasError"` } type OtelSpanRef struct { @@ -176,82 +168,61 @@ func (ref *OtelSpanRef) toString() string { func (item *SearchSpanReponseItem) GetValues() []interface{} { - timeObj, _ := time.Parse(time.RFC3339Nano, item.Timestamp) references := []OtelSpanRef{} - json.Unmarshal([]byte(item.References), &references) + jsonbody, _ := json.Marshal(item.References) + json.Unmarshal(jsonbody, &references) referencesStringArray := []string{} for _, item := range references { referencesStringArray = append(referencesStringArray, item.toString()) } - returnArray := []interface{}{int64(timeObj.UnixNano() / 1000000), item.SpanID, item.TraceID, item.ServiceName, item.Name, strconv.Itoa(int(item.Kind)), strconv.FormatInt(item.DurationNano, 10), item.TagsKeys, item.TagsValues, referencesStringArray, item.Events, item.HasError} + if item.Events == nil { + item.Events = []string{} + } + keys := make([]string, 0, len(item.TagMap)) + values := make([]string, 0, len(item.TagMap)) + + for k, v := range item.TagMap { + keys = append(keys, k) + values = append(values, v) + } + returnArray := []interface{}{item.TimeUnixNano, item.SpanID, item.TraceID, item.ServiceName, item.Name, strconv.Itoa(int(item.Kind)), strconv.FormatInt(item.DurationNano, 10), keys, values, referencesStringArray, item.Events, item.HasError} return returnArray } -type ServiceExternalItem struct { - Time string `json:"time,omitempty" db:"time,omitempty"` - Timestamp int64 `json:"timestamp,omitempty" db:"timestamp,omitempty"` - ExternalHttpUrl string `json:"externalHttpUrl,omitempty" db:"externalHttpUrl,omitempty"` - AvgDuration float32 `json:"avgDuration,omitempty" db:"avgDuration,omitempty"` - NumCalls int `json:"numCalls,omitempty" db:"numCalls,omitempty"` - CallRate float32 `json:"callRate,omitempty" db:"callRate,omitempty"` - NumErrors int `json:"numErrors" db:"numErrors"` - ErrorRate float32 `json:"errorRate" db:"errorRate"` -} - -type ServiceDBOverviewItem struct { - Time string `json:"time,omitempty" db:"time,omitempty"` - Timestamp int64 `json:"timestamp,omitempty" db:"timestamp,omitempty"` - DBSystem string `json:"dbSystem,omitempty" db:"dbSystem,omitempty"` - AvgDuration float32 `json:"avgDuration,omitempty" db:"avgDuration,omitempty"` - NumCalls int `json:"numCalls,omitempty" db:"numCalls,omitempty"` - CallRate float32 `json:"callRate,omitempty" db:"callRate,omitempty"` -} - type ServiceMapDependencyItem struct { - SpanId string `json:"spanId,omitempty" db:"spanID,omitempty"` - ParentSpanId string `json:"parentSpanId,omitempty" db:"parentSpanID,omitempty"` - ServiceName string `json:"serviceName,omitempty" db:"serviceName,omitempty"` + SpanId string `json:"spanId,omitempty" ch:"spanID"` + ParentSpanId string `json:"parentSpanId,omitempty" ch:"parentSpanID"` + ServiceName string `json:"serviceName,omitempty" ch:"serviceName"` } type UsageItem struct { - Time string `json:"time,omitempty" db:"time,omitempty"` - Timestamp int64 `json:"timestamp" db:"timestamp"` - Count int64 `json:"count" db:"count"` + Time time.Time `json:"time,omitempty" ch:"time"` + Timestamp uint64 `json:"timestamp" ch:"timestamp"` + Count uint64 `json:"count" ch:"count"` } type TopEndpointsItem struct { - Percentile50 float32 `json:"p50" db:"p50"` - Percentile95 float32 `json:"p95" db:"p95"` - Percentile99 float32 `json:"p99" db:"p99"` - NumCalls int `json:"numCalls" db:"numCalls"` - Name string `json:"name" db:"name"` -} - -type TagItem struct { - TagKeys string `json:"tagKeys" db:"tagKeys"` - TagCount int `json:"tagCount" db:"tagCount"` + Percentile50 float64 `json:"p50" ch:"p50"` + Percentile95 float64 `json:"p95" ch:"p95"` + Percentile99 float64 `json:"p99" ch:"p99"` + NumCalls uint64 `json:"numCalls" ch:"numCalls"` + Name string `json:"name" ch:"name"` } type TagFilters struct { - TagKeys string `json:"tagKeys" db:"tagKeys"` + TagKeys string `json:"tagKeys" ch:"tagKeys"` } type TagValues struct { - TagValues string `json:"tagValues" db:"tagValues"` + TagValues string `json:"tagValues" ch:"tagValues"` } type ServiceMapDependencyResponseItem struct { - Parent string `json:"parent,omitempty" db:"parent,omitempty"` - Child string `json:"child,omitempty" db:"child,omitempty"` - CallCount int `json:"callCount,omitempty" db:"callCount,omitempty"` -} - -type SpanSearchAggregatesResponseItem struct { - Timestamp int64 `json:"timestamp,omitempty" db:"timestamp" ` - Time string `json:"time,omitempty" db:"time"` - Value float32 `json:"value,omitempty" db:"value"` + Parent string `json:"parent,omitempty" ch:"parent"` + Child string `json:"child,omitempty" ch:"child"` + CallCount int `json:"callCount,omitempty" ch:"callCount"` } type GetFilteredSpansAggregatesResponse struct { @@ -263,10 +234,12 @@ type SpanAggregatesResponseItem struct { GroupBy map[string]float32 `json:"groupBy,omitempty"` } type SpanAggregatesDBResponseItem struct { - Timestamp int64 `json:"timestamp,omitempty" db:"timestamp" ` - Time string `json:"time,omitempty" db:"time"` - Value float32 `json:"value,omitempty" db:"value"` - GroupBy sql.NullString `json:"groupBy,omitempty" db:"groupBy"` + Timestamp int64 `ch:"timestamp" ` + Time time.Time `ch:"time"` + Value uint64 `ch:"value"` + FloatValue float32 `ch:"floatValue"` + Float64Value float64 `ch:"float64Value"` + GroupBy string `ch:"groupBy"` } type SetTTLResponseItem struct { @@ -274,12 +247,12 @@ type SetTTLResponseItem struct { } type DiskItem struct { - Name string `json:"name,omitempty" db:"name,omitempty"` - Type string `json:"type,omitempty" db:"type,omitempty"` + Name string `json:"name,omitempty" ch:"name"` + Type string `json:"type,omitempty" ch:"type"` } type DBResponseTTL struct { - EngineFull string `db:"engine_full"` + EngineFull string `ch:"engine_full"` } type GetTTLResponseItem struct { @@ -289,90 +262,81 @@ type GetTTLResponseItem struct { TracesMoveTime int `json:"traces_move_ttl_duration_hrs,omitempty"` } -type DBResponseMinMaxDuration struct { - MinDuration int `db:"min(durationNano)"` - MaxDuration int `db:"max(durationNano)"` -} - type DBResponseServiceName struct { - ServiceName string `db:"serviceName"` - Count int `db:"count"` + ServiceName string `ch:"serviceName"` + Count uint64 `ch:"count"` } type DBResponseHttpCode struct { - HttpCode string `db:"httpCode"` - Count int `db:"count"` + HttpCode string `ch:"httpCode"` + Count uint64 `ch:"count"` } type DBResponseHttpRoute struct { - HttpRoute string `db:"httpRoute"` - Count int `db:"count"` + HttpRoute string `ch:"httpRoute"` + Count uint64 `ch:"count"` } type DBResponseHttpUrl struct { - HttpUrl string `db:"httpUrl"` - Count int `db:"count"` + HttpUrl string `ch:"httpUrl"` + Count uint64 `ch:"count"` } type DBResponseHttpMethod struct { - HttpMethod string `db:"httpMethod"` - Count int `db:"count"` + HttpMethod string `ch:"httpMethod"` + Count uint64 `ch:"count"` } type DBResponseHttpHost struct { - HttpHost string `db:"httpHost"` - Count int `db:"count"` + HttpHost string `ch:"httpHost"` + Count uint64 `ch:"count"` } type DBResponseOperation struct { - Operation string `db:"name"` - Count int `db:"count"` + Operation string `ch:"name"` + Count uint64 `ch:"count"` } type DBResponseComponent struct { - Component sql.NullString `db:"component"` - Count int `db:"count"` -} - -type DBResponseErrors struct { - NumErrors int `db:"numErrors"` + Component string `ch:"component"` + Count uint64 `ch:"count"` } type DBResponseTotal struct { - NumTotal int `db:"numTotal"` + NumTotal uint64 `ch:"numTotal"` } type SpanFiltersResponse struct { - ServiceName map[string]int `json:"serviceName"` - Status map[string]int `json:"status"` - Duration map[string]int `json:"duration"` - Operation map[string]int `json:"operation"` - HttpCode map[string]int `json:"httpCode"` - HttpUrl map[string]int `json:"httpUrl"` - HttpMethod map[string]int `json:"httpMethod"` - HttpRoute map[string]int `json:"httpRoute"` - HttpHost map[string]int `json:"httpHost"` - Component map[string]int `json:"component"` + ServiceName map[string]uint64 `json:"serviceName"` + Status map[string]uint64 `json:"status"` + Duration map[string]uint64 `json:"duration"` + Operation map[string]uint64 `json:"operation"` + HttpCode map[string]uint64 `json:"httpCode"` + HttpUrl map[string]uint64 `json:"httpUrl"` + HttpMethod map[string]uint64 `json:"httpMethod"` + HttpRoute map[string]uint64 `json:"httpRoute"` + HttpHost map[string]uint64 `json:"httpHost"` + Component map[string]uint64 `json:"component"` } type Error struct { - ExceptionType string `json:"exceptionType" db:"exceptionType"` - ExceptionMsg string `json:"exceptionMessage" db:"exceptionMessage"` - ExceptionCount int64 `json:"exceptionCount" db:"exceptionCount"` - LastSeen time.Time `json:"lastSeen" db:"lastSeen"` - FirstSeen time.Time `json:"firstSeen" db:"firstSeen"` - ServiceName string `json:"serviceName" db:"serviceName"` + ExceptionType string `json:"exceptionType" ch:"exceptionType"` + ExceptionMsg string `json:"exceptionMessage" ch:"exceptionMessage"` + ExceptionCount uint64 `json:"exceptionCount" ch:"exceptionCount"` + LastSeen time.Time `json:"lastSeen" ch:"lastSeen"` + FirstSeen time.Time `json:"firstSeen" ch:"firstSeen"` + ServiceName string `json:"serviceName" ch:"serviceName"` } type ErrorWithSpan struct { - ErrorID string `json:"errorId" db:"errorID"` - ExceptionType string `json:"exceptionType" db:"exceptionType"` - ExcepionStacktrace string `json:"excepionStacktrace" db:"excepionStacktrace"` - ExceptionEscaped string `json:"exceptionEscaped" db:"exceptionEscaped"` - ExceptionMsg string `json:"exceptionMessage" db:"exceptionMessage"` - Timestamp time.Time `json:"timestamp" db:"timestamp"` - SpanID string `json:"spanID" db:"spanID"` - TraceID string `json:"traceID" db:"traceID"` - ServiceName string `json:"serviceName" db:"serviceName"` - NewerErrorID string `json:"newerErrorId" db:"newerErrorId"` - OlderErrorID string `json:"olderErrorId" db:"olderErrorId"` + ErrorID string `json:"errorId" ch:"errorID"` + ExceptionType string `json:"exceptionType" ch:"exceptionType"` + ExceptionStacktrace string `json:"exceptionStacktrace" ch:"exceptionStacktrace"` + ExceptionEscaped string `json:"exceptionEscaped" ch:"exceptionEscaped"` + ExceptionMsg string `json:"exceptionMessage" ch:"exceptionMessage"` + Timestamp time.Time `json:"timestamp" ch:"timestamp"` + SpanID string `json:"spanID" ch:"spanID"` + TraceID string `json:"traceID" ch:"traceID"` + ServiceName string `json:"serviceName" ch:"serviceName"` + NewerErrorID string `json:"newerErrorId" ch:"newerErrorId"` + OlderErrorID string `json:"olderErrorId" ch:"olderErrorId"` } diff --git a/pkg/query-service/model/userPreferences.go b/pkg/query-service/model/userPreferences.go deleted file mode 100644 index 0a6ca2d1e4..0000000000 --- a/pkg/query-service/model/userPreferences.go +++ /dev/null @@ -1,27 +0,0 @@ -package model - -type UserPreferences struct { - Id int `json:"id" db:"id"` - Uuid string `json:"uuid" db:"uuid"` - IsAnonymous bool `json:"isAnonymous" db:"isAnonymous"` - HasOptedUpdates bool `json:"hasOptedUpdates" db:"hasOptedUpdates"` -} - -func (up *UserPreferences) SetIsAnonymous(isAnonymous bool) { - up.IsAnonymous = isAnonymous -} -func (up *UserPreferences) SetHasOptedUpdate(hasOptedUpdates bool) { - up.HasOptedUpdates = hasOptedUpdates -} -func (up *UserPreferences) GetIsAnonymous() bool { - return up.IsAnonymous -} -func (up *UserPreferences) GetHasOptedUpdate() bool { - return up.HasOptedUpdates -} -func (up *UserPreferences) GetId() int { - return up.Id -} -func (up *UserPreferences) GetUUID() string { - return up.Uuid -} diff --git a/pkg/query-service/telemetry/ignoredPaths.go b/pkg/query-service/telemetry/ignoredPaths.go index e6adf197ff..00ceb1c79a 100644 --- a/pkg/query-service/telemetry/ignoredPaths.go +++ b/pkg/query-service/telemetry/ignoredPaths.go @@ -2,8 +2,8 @@ package telemetry func IgnoredPaths() map[string]struct{} { ignoredPaths := map[string]struct{}{ - "/api/v1/tags": struct{}{}, - "/api/v1/version": struct{}{}, + "/api/v1/tags": {}, + "/api/v1/version": {}, } return ignoredPaths diff --git a/pkg/query-service/telemetry/telemetry.go b/pkg/query-service/telemetry/telemetry.go index 9dea99f94f..1f5360fa40 100644 --- a/pkg/query-service/telemetry/telemetry.go +++ b/pkg/query-service/telemetry/telemetry.go @@ -19,7 +19,7 @@ const ( TELEMETRY_EVENT_INPRODUCT_FEEDBACK = "InProduct Feeback Submitted" TELEMETRY_EVENT_NUMBER_OF_SERVICES = "Number of Services" TELEMETRY_EVENT_HEART_BEAT = "Heart Beat" - TELEMETRY_EVENT_USER_PREFERENCES = "User Preferences" + TELEMETRY_EVENT_ORG_SETTINGS = "Org Settings" ) const api_key = "4Gmoa4ixJAUHx2BpJxsjwA1bEfnwEeRz" diff --git a/pkg/query-service/tests/auth_test.go b/pkg/query-service/tests/auth_test.go new file mode 100644 index 0000000000..8adde42f75 --- /dev/null +++ b/pkg/query-service/tests/auth_test.go @@ -0,0 +1,125 @@ +package tests + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + "go.signoz.io/query-service/auth" + "go.signoz.io/query-service/model" +) + +func invite(t *testing.T, email string) *model.InviteResponse { + q := endpoint + fmt.Sprintf("/api/v1/invite?email=%s", email) + resp, err := client.Get(q) + require.NoError(t, err) + + defer resp.Body.Close() + b, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + + var inviteResp model.InviteResponse + err = json.Unmarshal(b, &inviteResp) + require.NoError(t, err) + + return &inviteResp +} + +func register(email, password, token string) (string, error) { + q := endpoint + fmt.Sprintf("/api/v1/register") + + req := auth.RegisterRequest{ + Email: email, + Password: password, + InviteToken: token, + } + + b, err := json.Marshal(req) + if err != nil { + return "", err + } + resp, err := client.Post(q, "application/json", bytes.NewBuffer(b)) + if err != nil { + return "", err + } + + defer resp.Body.Close() + b, err = ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + + return string(b), nil +} + +func login(email, password, refreshToken string) (*model.LoginResponse, error) { + q := endpoint + fmt.Sprintf("/api/v1/login") + + req := model.LoginRequest{ + Email: email, + Password: password, + RefreshToken: refreshToken, + } + + b, err := json.Marshal(req) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal") + } + resp, err := client.Post(q, "application/json", bytes.NewBuffer(b)) + if err != nil { + return nil, errors.Wrap(err, "failed to post") + } + + defer resp.Body.Close() + b, err = ioutil.ReadAll(resp.Body) + if err != nil { + return nil, errors.Wrap(err, "failed to read body") + } + + loginResp := &model.LoginResponse{} + err = json.Unmarshal(b, loginResp) + if err != nil { + return nil, errors.Wrap(err, "failed to unmarshal") + } + + return loginResp, nil +} + +func TestAuthInviteAPI(t *testing.T) { + t.Skip() + email := "abc@signoz.io" + resp := invite(t, email) + require.Equal(t, email, resp.Email) + require.NotNil(t, resp.InviteToken) +} + +func TestAuthRegisterAPI(t *testing.T) { + email := "alice@signoz.io" + resp, err := register(email, "password", "") + require.NoError(t, err) + require.Contains(t, resp, "user registered successfully") + +} + +func TestAuthLoginAPI(t *testing.T) { + t.Skip() + email := "abc-login@signoz.io" + password := "password123" + inv := invite(t, email) + + resp, err := register(email, password, inv.InviteToken) + require.NoError(t, err) + require.Contains(t, resp, "user registered successfully") + + loginResp, err := login(email, password, "") + require.NoError(t, err) + + loginResp2, err := login("", "", loginResp.RefreshJwt) + require.NoError(t, err) + + require.NotNil(t, loginResp2.AccessJwt) +} diff --git a/pkg/query-service/tests/test-deploy/data/signoz.db b/pkg/query-service/tests/test-deploy/data/signoz.db deleted file mode 100644 index c19319ab34..0000000000 Binary files a/pkg/query-service/tests/test-deploy/data/signoz.db and /dev/null differ diff --git a/pkg/query-service/tests/test-deploy/docker-compose.arm.yaml b/pkg/query-service/tests/test-deploy/docker-compose.arm.yaml index a5d4e28796..df1df0e970 100644 --- a/pkg/query-service/tests/test-deploy/docker-compose.arm.yaml +++ b/pkg/query-service/tests/test-deploy/docker-compose.arm.yaml @@ -18,7 +18,7 @@ services: retries: 3 alertmanager: - image: signoz/alertmanager:0.6.0 + image: signoz/alertmanager:0.23.0-0.1 depends_on: - query-service restart: on-failure @@ -37,7 +37,7 @@ services: ports: - "8180:8080" environment: - - ClickHouseUrl=tcp://clickhouse:9000 + - ClickHouseUrl=tcp://clickhouse:9000/?database=signoz_traces - STORAGE=clickhouse - GODEBUG=netdns=go - TELEMETRY_ENABLED=true @@ -51,7 +51,7 @@ services: condition: service_healthy otel-collector: - image: signoz/otelcontribcol:0.43.0 + image: signoz/otelcontribcol:0.43.0-0.1 command: ["--config=/etc/otel-collector-config.yaml"] volumes: - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml @@ -64,7 +64,7 @@ services: condition: service_healthy otel-collector-metrics: - image: signoz/otelcontribcol:0.43.0 + image: signoz/otelcontribcol:0.43.0-0.1 command: ["--config=/etc/otel-collector-metrics-config.yaml"] volumes: - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml diff --git a/pkg/query-service/tests/test-deploy/docker-compose.yaml b/pkg/query-service/tests/test-deploy/docker-compose.yaml index c88e3544ec..4f5dd7033c 100644 --- a/pkg/query-service/tests/test-deploy/docker-compose.yaml +++ b/pkg/query-service/tests/test-deploy/docker-compose.yaml @@ -16,9 +16,12 @@ services: interval: 30s timeout: 5s retries: 3 + ports: + - "9000:9000" + - "8123:8123" alertmanager: - image: signoz/alertmanager:0.6.0 + image: signoz/alertmanager:0.23.0-0.1 depends_on: - query-service restart: on-failure @@ -39,7 +42,7 @@ services: ports: - "8180:8080" environment: - - ClickHouseUrl=tcp://clickhouse:9000 + - ClickHouseUrl=tcp://clickhouse:9000/?database=signoz_traces - STORAGE=clickhouse - GODEBUG=netdns=go - TELEMETRY_ENABLED=true @@ -53,7 +56,7 @@ services: condition: service_healthy otel-collector: - image: signoz/otelcontribcol:0.43.0 + image: signoz/otelcontribcol:0.43.0-0.1 command: ["--config=/etc/otel-collector-config.yaml"] volumes: - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml @@ -66,7 +69,7 @@ services: condition: service_healthy otel-collector-metrics: - image: signoz/otelcontribcol:0.43.0 + image: signoz/otelcontribcol:0.43.0-0.1 command: ["--config=/etc/otel-collector-metrics-config.yaml"] volumes: - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml diff --git a/pkg/query-service/tests/test-deploy/otel-collector-config.yaml b/pkg/query-service/tests/test-deploy/otel-collector-config.yaml index a4a2641daa..128d0fb6a2 100644 --- a/pkg/query-service/tests/test-deploy/otel-collector-config.yaml +++ b/pkg/query-service/tests/test-deploy/otel-collector-config.yaml @@ -27,6 +27,12 @@ processors: signozspanmetrics/prometheus: metrics_exporter: prometheus latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ] + dimensions_cache_size: 10000 + dimensions: + - name: service.namespace + default: default + - name: deployment.environment + default: default # memory_limiter: # # Same as --mem-ballast-size-mib CLI argument # ballast_size_mib: 683 @@ -44,7 +50,7 @@ extensions: zpages: {} exporters: clickhouse: - datasource: tcp://clickhouse:9000 + datasource: tcp://clickhouse:9000/?database=signoz_traces clickhousemetricswrite: endpoint: tcp://clickhouse:9000/?database=signoz_metrics resource_to_telemetry_conversion: diff --git a/sample-apps/hotrod/hotrod-delete.sh b/sample-apps/hotrod/hotrod-delete.sh index f73f89c1a6..a7d88ebc07 100755 --- a/sample-apps/hotrod/hotrod-delete.sh +++ b/sample-apps/hotrod/hotrod-delete.sh @@ -16,5 +16,5 @@ fi if [ $? -ne 0 ]; then echo "❌ Failed to delete HotROD sample application" else - echo "✅ Succesfully deleted HotROD sample application" + echo "✅ Successfully deleted HotROD sample application" fi diff --git a/sample-apps/hotrod/hotrod-install.sh b/sample-apps/hotrod/hotrod-install.sh index f6f3845205..dc839d0535 100755 --- a/sample-apps/hotrod/hotrod-install.sh +++ b/sample-apps/hotrod/hotrod-install.sh @@ -48,5 +48,5 @@ kubectl apply --namespace="${HOTROD_NAMESPACE}" -f <( \ if [ $? -ne 0 ]; then echo "❌ Failed to deploy HotROD sample application" else - echo "✅ Succesfully deployed HotROD sample application" + echo "✅ Successfully deployed HotROD sample application" fi