[privacy] make observability local-only
This commit is contained in:
29
.env.example
29
.env.example
@@ -210,33 +210,8 @@ ANDROID_SHA256_CERT_FINGERPRINT=AC:73:8E:DE:EB:56:EA:CC:10:87:02:A7:65:37:7B:38:
|
|||||||
# for mobile apps
|
# for mobile apps
|
||||||
# FCM_SERVER_KEY=
|
# FCM_SERVER_KEY=
|
||||||
|
|
||||||
### APM and Error Monitoring configurations
|
### Remote observability
|
||||||
## Elastic APM
|
# Remote error reporting and APM agents are intentionally disabled in this build.
|
||||||
## https://www.elastic.co/guide/en/apm/agent/ruby/current/getting-started-rails.html
|
|
||||||
# ELASTIC_APM_SERVER_URL=
|
|
||||||
# ELASTIC_APM_SECRET_TOKEN=
|
|
||||||
|
|
||||||
## Sentry
|
|
||||||
# SENTRY_DSN=
|
|
||||||
|
|
||||||
|
|
||||||
## Scout
|
|
||||||
## https://scoutapm.com/docs/ruby/configuration
|
|
||||||
# SCOUT_KEY=YOURKEY
|
|
||||||
# SCOUT_NAME=YOURAPPNAME (Production)
|
|
||||||
# SCOUT_MONITOR=true
|
|
||||||
|
|
||||||
## NewRelic
|
|
||||||
# https://docs.newrelic.com/docs/agents/ruby-agent/configuration/ruby-agent-configuration/
|
|
||||||
# NEW_RELIC_LICENSE_KEY=
|
|
||||||
# Set this to true to allow newrelic apm to send logs.
|
|
||||||
# This is turned off by default.
|
|
||||||
# NEW_RELIC_APPLICATION_LOGGING_ENABLED=
|
|
||||||
|
|
||||||
## Datadog
|
|
||||||
## https://github.com/DataDog/dd-trace-rb/blob/master/docs/GettingStarted.md#environment-variables
|
|
||||||
# DD_TRACE_AGENT_URL=
|
|
||||||
|
|
||||||
|
|
||||||
# MaxMindDB API key to download GeoLite2 City database
|
# MaxMindDB API key to download GeoLite2 City database
|
||||||
# IP_LOOKUP_API_KEY=
|
# IP_LOOKUP_API_KEY=
|
||||||
|
|||||||
8
Gemfile
8
Gemfile
@@ -124,14 +124,6 @@ gem 'google-cloud-translate-v3', '>= 0.7.0'
|
|||||||
##-- apm and error monitoring ---#
|
##-- apm and error monitoring ---#
|
||||||
# loaded only when environment variables are set.
|
# loaded only when environment variables are set.
|
||||||
# ref application.rb
|
# ref application.rb
|
||||||
gem 'datadog', '~> 2.0', require: false
|
|
||||||
gem 'elastic-apm', require: false
|
|
||||||
gem 'newrelic_rpm', require: false
|
|
||||||
gem 'newrelic-sidekiq-metrics', '>= 1.6.2', require: false
|
|
||||||
gem 'scout_apm', require: false
|
|
||||||
gem 'sentry-rails', '>= 5.19.0', require: false
|
|
||||||
gem 'sentry-ruby', require: false
|
|
||||||
gem 'sentry-sidekiq', '>= 5.19.0', require: false
|
|
||||||
|
|
||||||
##-- background job processing --##
|
##-- background job processing --##
|
||||||
gem 'sidekiq', '~> 7.3.10'
|
gem 'sidekiq', '~> 7.3.10'
|
||||||
|
|||||||
59
Gemfile.lock
59
Gemfile.lock
@@ -187,14 +187,6 @@ GEM
|
|||||||
activerecord (>= 5.a)
|
activerecord (>= 5.a)
|
||||||
database_cleaner-core (~> 2.0.0)
|
database_cleaner-core (~> 2.0.0)
|
||||||
database_cleaner-core (2.0.1)
|
database_cleaner-core (2.0.1)
|
||||||
datadog (2.38.0)
|
|
||||||
cgi
|
|
||||||
datadog-ruby_core_source (~> 3.5, >= 3.5.3)
|
|
||||||
libdatadog (~> 36.0.0.1.0)
|
|
||||||
libddwaf (~> 1.30.0.0.0)
|
|
||||||
logger
|
|
||||||
msgpack
|
|
||||||
datadog-ruby_core_source (3.5.3)
|
|
||||||
date (3.5.1)
|
date (3.5.1)
|
||||||
debug (1.11.1)
|
debug (1.11.1)
|
||||||
irb (~> 1.10)
|
irb (~> 1.10)
|
||||||
@@ -263,10 +255,6 @@ GEM
|
|||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
ecma-re-validator (0.4.0)
|
ecma-re-validator (0.4.0)
|
||||||
regexp_parser (~> 2.2)
|
regexp_parser (~> 2.2)
|
||||||
elastic-apm (4.6.2)
|
|
||||||
concurrent-ruby (~> 1.0)
|
|
||||||
http (>= 3.0)
|
|
||||||
ruby2_keywords
|
|
||||||
email-provider-info (0.0.1)
|
email-provider-info (0.0.1)
|
||||||
email_reply_trimmer (0.1.13)
|
email_reply_trimmer (0.1.13)
|
||||||
erb (6.0.6)
|
erb (6.0.6)
|
||||||
@@ -315,9 +303,6 @@ GEM
|
|||||||
ffi (1.17.2-arm64-darwin)
|
ffi (1.17.2-arm64-darwin)
|
||||||
ffi (1.17.2-x86_64-darwin)
|
ffi (1.17.2-x86_64-darwin)
|
||||||
ffi (1.17.2-x86_64-linux-gnu)
|
ffi (1.17.2-x86_64-linux-gnu)
|
||||||
ffi-compiler (1.0.1)
|
|
||||||
ffi (>= 1.0.0)
|
|
||||||
rake
|
|
||||||
firecrawl-sdk (1.4.1)
|
firecrawl-sdk (1.4.1)
|
||||||
flag_shih_tzu (0.3.23)
|
flag_shih_tzu (0.3.23)
|
||||||
foreman (0.87.2)
|
foreman (0.87.2)
|
||||||
@@ -418,15 +403,9 @@ GEM
|
|||||||
logger
|
logger
|
||||||
html2text (0.4.0)
|
html2text (0.4.0)
|
||||||
nokogiri (>= 1.0, < 2.0)
|
nokogiri (>= 1.0, < 2.0)
|
||||||
http (5.1.1)
|
|
||||||
addressable (~> 2.8)
|
|
||||||
http-cookie (~> 1.0)
|
|
||||||
http-form_data (~> 2.2)
|
|
||||||
llhttp-ffi (~> 0.4.0)
|
|
||||||
http-accept (1.7.0)
|
http-accept (1.7.0)
|
||||||
http-cookie (1.0.5)
|
http-cookie (1.0.5)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
http-form_data (2.3.0)
|
|
||||||
httparty (0.24.0)
|
httparty (0.24.0)
|
||||||
csv
|
csv
|
||||||
mini_mime (>= 1.0.0)
|
mini_mime (>= 1.0.0)
|
||||||
@@ -501,26 +480,12 @@ GEM
|
|||||||
logger (~> 1.6)
|
logger (~> 1.6)
|
||||||
letter_opener (1.10.0)
|
letter_opener (1.10.0)
|
||||||
launchy (>= 2.2, < 4)
|
launchy (>= 2.2, < 4)
|
||||||
libdatadog (36.0.0.1.0)
|
|
||||||
libdatadog (36.0.0.1.0-arm64-darwin)
|
|
||||||
libdatadog (36.0.0.1.0-x86_64-linux)
|
|
||||||
libddwaf (1.30.0.0.2)
|
|
||||||
ffi (~> 1.0)
|
|
||||||
libddwaf (1.30.0.0.2-arm64-darwin)
|
|
||||||
ffi (~> 1.0)
|
|
||||||
libddwaf (1.30.0.0.2-x86_64-darwin)
|
|
||||||
ffi (~> 1.0)
|
|
||||||
libddwaf (1.30.0.0.2-x86_64-linux)
|
|
||||||
ffi (~> 1.0)
|
|
||||||
line-bot-api (1.28.0)
|
line-bot-api (1.28.0)
|
||||||
lint_roller (1.1.0)
|
lint_roller (1.1.0)
|
||||||
liquid (5.4.0)
|
liquid (5.4.0)
|
||||||
listen (3.8.0)
|
listen (3.8.0)
|
||||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||||
rb-inotify (~> 0.9, >= 0.9.10)
|
rb-inotify (~> 0.9, >= 0.9.10)
|
||||||
llhttp-ffi (0.4.0)
|
|
||||||
ffi-compiler (~> 1.0)
|
|
||||||
rake (~> 13.0)
|
|
||||||
logger (1.7.0)
|
logger (1.7.0)
|
||||||
lograge (0.14.0)
|
lograge (0.14.0)
|
||||||
actionpack (>= 4)
|
actionpack (>= 4)
|
||||||
@@ -570,11 +535,6 @@ GEM
|
|||||||
net-smtp (0.3.4)
|
net-smtp (0.3.4)
|
||||||
net-protocol
|
net-protocol
|
||||||
netrc (0.11.0)
|
netrc (0.11.0)
|
||||||
newrelic-sidekiq-metrics (1.6.2)
|
|
||||||
newrelic_rpm (>= 8.0.0)
|
|
||||||
sidekiq
|
|
||||||
newrelic_rpm (9.6.0)
|
|
||||||
base64
|
|
||||||
nio4r (2.7.5)
|
nio4r (2.7.5)
|
||||||
nokogiri (1.19.4)
|
nokogiri (1.19.4)
|
||||||
mini_portile2 (~> 2.8.2)
|
mini_portile2 (~> 2.8.2)
|
||||||
@@ -868,8 +828,6 @@ GEM
|
|||||||
sprockets (> 3.0)
|
sprockets (> 3.0)
|
||||||
sprockets-rails
|
sprockets-rails
|
||||||
tilt
|
tilt
|
||||||
scout_apm (5.3.3)
|
|
||||||
parser
|
|
||||||
scss_lint (0.60.0)
|
scss_lint (0.60.0)
|
||||||
sass (~> 3.5, >= 3.5.5)
|
sass (~> 3.5, >= 3.5.5)
|
||||||
searchkick (5.5.2)
|
searchkick (5.5.2)
|
||||||
@@ -880,15 +838,6 @@ GEM
|
|||||||
activerecord (>= 4)
|
activerecord (>= 4)
|
||||||
activesupport (>= 4)
|
activesupport (>= 4)
|
||||||
selectize-rails (0.12.6)
|
selectize-rails (0.12.6)
|
||||||
sentry-rails (5.19.0)
|
|
||||||
railties (>= 5.0)
|
|
||||||
sentry-ruby (~> 5.19.0)
|
|
||||||
sentry-ruby (5.19.0)
|
|
||||||
bigdecimal
|
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
||||||
sentry-sidekiq (5.19.0)
|
|
||||||
sentry-ruby (~> 5.19.0)
|
|
||||||
sidekiq (>= 3.0)
|
|
||||||
sexp_processor (4.17.0)
|
sexp_processor (4.17.0)
|
||||||
shopify_api (14.8.0)
|
shopify_api (14.8.0)
|
||||||
activesupport
|
activesupport
|
||||||
@@ -1072,7 +1021,6 @@ DEPENDENCIES
|
|||||||
commonmarker
|
commonmarker
|
||||||
csv-safe
|
csv-safe
|
||||||
database_cleaner
|
database_cleaner
|
||||||
datadog (~> 2.0)
|
|
||||||
debug (~> 1.8)
|
debug (~> 1.8)
|
||||||
devise (>= 4.9.4)
|
devise (>= 4.9.4)
|
||||||
devise-secure_password (= 2.2.1)
|
devise-secure_password (= 2.2.1)
|
||||||
@@ -1080,7 +1028,6 @@ DEPENDENCIES
|
|||||||
devise_token_auth (>= 1.2.3)
|
devise_token_auth (>= 1.2.3)
|
||||||
dotenv-rails (>= 3.0.0)
|
dotenv-rails (>= 3.0.0)
|
||||||
down
|
down
|
||||||
elastic-apm
|
|
||||||
email-provider-info
|
email-provider-info
|
||||||
email_reply_trimmer
|
email_reply_trimmer
|
||||||
facebook-messenger
|
facebook-messenger
|
||||||
@@ -1123,8 +1070,6 @@ DEPENDENCIES
|
|||||||
mock_redis
|
mock_redis
|
||||||
neighbor
|
neighbor
|
||||||
net-smtp (~> 0.3.4)
|
net-smtp (~> 0.3.4)
|
||||||
newrelic-sidekiq-metrics (>= 1.6.2)
|
|
||||||
newrelic_rpm
|
|
||||||
omniauth (>= 2.1.2)
|
omniauth (>= 2.1.2)
|
||||||
omniauth-google-oauth2 (>= 1.1.3)
|
omniauth-google-oauth2 (>= 1.1.3)
|
||||||
omniauth-oauth2
|
omniauth-oauth2
|
||||||
@@ -1161,13 +1106,9 @@ DEPENDENCIES
|
|||||||
ruby-openai
|
ruby-openai
|
||||||
ruby_llm (>= 1.14.1)
|
ruby_llm (>= 1.14.1)
|
||||||
ruby_llm-schema
|
ruby_llm-schema
|
||||||
scout_apm
|
|
||||||
scss_lint
|
scss_lint
|
||||||
searchkick
|
searchkick
|
||||||
seed_dump
|
seed_dump
|
||||||
sentry-rails (>= 5.19.0)
|
|
||||||
sentry-ruby
|
|
||||||
sentry-sidekiq (>= 5.19.0)
|
|
||||||
shopify_api
|
shopify_api
|
||||||
shoulda-matchers
|
shoulda-matchers
|
||||||
sidekiq (~> 7.3.10)
|
sidekiq (~> 7.3.10)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { unref } from 'vue';
|
import { unref } from 'vue';
|
||||||
import camelcaseKeys from 'camelcase-keys';
|
import camelcaseKeys from 'camelcase-keys';
|
||||||
import snakecaseKeys from 'snakecase-keys';
|
import snakecaseKeys from 'snakecase-keys';
|
||||||
import * as Sentry from '@sentry/vue';
|
import ExceptionTracker from 'shared/helpers/ExceptionTracker';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vue composable that converts object keys to camelCase
|
* Vue composable that converts object keys to camelCase
|
||||||
@@ -17,12 +17,12 @@ export function useCamelCase(payload, options) {
|
|||||||
const unrefPayload = unref(payload);
|
const unrefPayload = unref(payload);
|
||||||
return camelcaseKeys(unrefPayload, options);
|
return camelcaseKeys(unrefPayload, options);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Sentry.setContext('transform-keys-error', {
|
ExceptionTracker.setContext('transform-keys-error', {
|
||||||
payload,
|
payload,
|
||||||
options,
|
options,
|
||||||
op: 'camelCase',
|
op: 'camelCase',
|
||||||
});
|
});
|
||||||
Sentry.captureException(e);
|
ExceptionTracker.captureException(e);
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,12 +39,12 @@ export function useSnakeCase(payload, options) {
|
|||||||
const unrefPayload = unref(payload);
|
const unrefPayload = unref(payload);
|
||||||
return snakecaseKeys(unrefPayload, options);
|
return snakecaseKeys(unrefPayload, options);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Sentry.setContext('transform-keys-error', {
|
ExceptionTracker.setContext('transform-keys-error', {
|
||||||
payload,
|
payload,
|
||||||
options,
|
options,
|
||||||
op: 'snakeCase',
|
op: 'snakeCase',
|
||||||
});
|
});
|
||||||
Sentry.captureException(e);
|
ExceptionTracker.captureException(e);
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
messageSchema,
|
messageSchema,
|
||||||
Selection,
|
Selection,
|
||||||
} from '@chatwoot/prosemirror-schema';
|
} from '@chatwoot/prosemirror-schema';
|
||||||
import * as Sentry from '@sentry/vue';
|
import ExceptionTracker from 'shared/helpers/ExceptionTracker';
|
||||||
import camelcaseKeys from 'camelcase-keys';
|
import camelcaseKeys from 'camelcase-keys';
|
||||||
import { FORMATTING, MARKDOWN_PATTERNS } from 'dashboard/constants/editor';
|
import { FORMATTING, MARKDOWN_PATTERNS } from 'dashboard/constants/editor';
|
||||||
import { INBOX_TYPES, TWILIO_CHANNEL_MEDIUM } from 'dashboard/helper/inbox';
|
import { INBOX_TYPES, TWILIO_CHANNEL_MEDIUM } from 'dashboard/helper/inbox';
|
||||||
@@ -117,7 +117,7 @@ export function cleanSignature(signature) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
Sentry.captureException(e);
|
ExceptionTracker.captureException(e);
|
||||||
// The parser can break on some cases
|
// The parser can break on some cases
|
||||||
// for example, Token type `hr` not supported by Markdown parser
|
// for example, Token type `hr` not supported by Markdown parser
|
||||||
return signature;
|
return signature;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import ComboBox from 'dashboard/components-next/combobox/ComboBox.vue';
|
|||||||
import { META_RESTRICTION_STATUS_URL } from 'dashboard/constants/globals';
|
import { META_RESTRICTION_STATUS_URL } from 'dashboard/constants/globals';
|
||||||
import { parseAPIErrorResponse } from 'dashboard/store/utils/api';
|
import { parseAPIErrorResponse } from 'dashboard/store/utils/api';
|
||||||
|
|
||||||
import * as Sentry from '@sentry/vue';
|
import ExceptionTracker from 'shared/helpers/ExceptionTracker';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -113,7 +113,7 @@ export default {
|
|||||||
if (error.name === 'ScriptLoaderError') {
|
if (error.name === 'ScriptLoaderError') {
|
||||||
useAlert(this.$t('INBOX_MGMT.DETAILS.ERROR_FB_LOADING'));
|
useAlert(this.$t('INBOX_MGMT.DETAILS.ERROR_FB_LOADING'));
|
||||||
} else {
|
} else {
|
||||||
Sentry.captureException(error);
|
ExceptionTracker.captureException(error);
|
||||||
useAlert(this.$t('INBOX_MGMT.DETAILS.ERROR_FB_AUTH'));
|
useAlert(this.$t('INBOX_MGMT.DETAILS.ERROR_FB_AUTH'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useAlert } from 'dashboard/composables';
|
|||||||
|
|
||||||
import { loadScript } from 'dashboard/helper/DOMHelpers';
|
import { loadScript } from 'dashboard/helper/DOMHelpers';
|
||||||
import { buildFacebookLoginScopes } from 'dashboard/helper/facebookScopes';
|
import { buildFacebookLoginScopes } from 'dashboard/helper/facebookScopes';
|
||||||
import * as Sentry from '@sentry/vue';
|
import ExceptionTracker from 'shared/helpers/ExceptionTracker';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -62,7 +62,7 @@ export default {
|
|||||||
useAlert(this.$t('INBOX_MGMT.DETAILS.ERROR_FB_LOADING'));
|
useAlert(this.$t('INBOX_MGMT.DETAILS.ERROR_FB_LOADING'));
|
||||||
} else {
|
} else {
|
||||||
// if the error was anything else, we capture it and show a toast
|
// if the error was anything else, we capture it and show a toast
|
||||||
Sentry.captureException(error);
|
ExceptionTracker.captureException(error);
|
||||||
useAlert(this.$t('INBOX_MGMT.DETAILS.ERROR_FB_AUTH'));
|
useAlert(this.$t('INBOX_MGMT.DETAILS.ERROR_FB_AUTH'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import Icon from 'next/icon/Icon.vue';
|
import Icon from 'next/icon/Icon.vue';
|
||||||
import * as Sentry from '@sentry/vue';
|
import ExceptionTracker from 'shared/helpers/ExceptionTracker';
|
||||||
import FormSelect from 'v3/components/Form/Select.vue';
|
import FormSelect from 'v3/components/Form/Select.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -57,7 +57,7 @@ const playAudio = async () => {
|
|||||||
audio.src = `/audio/dashboard/${selectedValue.value}.mp3`;
|
audio.src = `/audio/dashboard/${selectedValue.value}.mp3`;
|
||||||
await audio.play();
|
await audio.play();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Sentry.captureException(error);
|
ExceptionTracker.captureException(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import types from '../../mutation-types';
|
import types from '../../mutation-types';
|
||||||
import * as Sentry from '@sentry/vue';
|
import ExceptionTracker from 'shared/helpers/ExceptionTracker';
|
||||||
|
|
||||||
export const mutations = {
|
export const mutations = {
|
||||||
[types.SET_CONTACT_UI_FLAG]($state, data) {
|
[types.SET_CONTACT_UI_FLAG]($state, data) {
|
||||||
@@ -81,11 +81,11 @@ export const mutations = {
|
|||||||
try {
|
try {
|
||||||
availabilityStatus = data[element.id];
|
availabilityStatus = data[element.id];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Sentry.setContext('contact is undefined', {
|
ExceptionTracker.setContext('contact is undefined', {
|
||||||
records: $state.records,
|
records: $state.records,
|
||||||
data: data,
|
data: data,
|
||||||
});
|
});
|
||||||
Sentry.captureException(error);
|
ExceptionTracker.captureException(error);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
} from './helpers/actionHelpers';
|
} from './helpers/actionHelpers';
|
||||||
import messageReadActions from './actions/messageReadActions';
|
import messageReadActions from './actions/messageReadActions';
|
||||||
import messageTranslateActions from './actions/messageTranslateActions';
|
import messageTranslateActions from './actions/messageTranslateActions';
|
||||||
import * as Sentry from '@sentry/vue';
|
import ExceptionTracker from 'shared/helpers/ExceptionTracker';
|
||||||
import {
|
import {
|
||||||
handleVoiceCallCreated,
|
handleVoiceCallCreated,
|
||||||
handleVoiceCallUpdated,
|
handleVoiceCallUpdated,
|
||||||
@@ -114,10 +114,10 @@ const actions = {
|
|||||||
attachments = data.payload;
|
attachments = data.payload;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// in case of error, log the error and continue
|
// in case of error, log the error and continue
|
||||||
Sentry.setContext('Conversation', {
|
ExceptionTracker.setContext('Conversation', {
|
||||||
id: conversationId,
|
id: conversationId,
|
||||||
});
|
});
|
||||||
Sentry.captureException(error);
|
ExceptionTracker.captureException(error);
|
||||||
} finally {
|
} finally {
|
||||||
// we run the commit even if the request fails
|
// we run the commit even if the request fails
|
||||||
// this ensures that the `attachment` variable is always present on chat
|
// this ensures that the `attachment` variable is always present on chat
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { createPinia } from 'pinia';
|
|||||||
import router, { initalizeRouter } from 'dashboard/routes';
|
import router, { initalizeRouter } from 'dashboard/routes';
|
||||||
import store from 'dashboard/store';
|
import store from 'dashboard/store';
|
||||||
import constants from 'dashboard/constants/globals';
|
import constants from 'dashboard/constants/globals';
|
||||||
import * as Sentry from '@sentry/vue';
|
|
||||||
import {
|
import {
|
||||||
initializeAnalyticsEvents,
|
initializeAnalyticsEvents,
|
||||||
initializeChatwootEvents,
|
initializeChatwootEvents,
|
||||||
@@ -49,31 +48,6 @@ app.use(store);
|
|||||||
app.use(pinia);
|
app.use(pinia);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
// [VITE] Disabled this, need to renable later
|
|
||||||
if (window.errorLoggingConfig) {
|
|
||||||
Sentry.init({
|
|
||||||
app,
|
|
||||||
dsn: window.errorLoggingConfig,
|
|
||||||
denyUrls: [
|
|
||||||
// Chrome extensions
|
|
||||||
/^chrome:\/\//i,
|
|
||||||
/chrome-extension:/i,
|
|
||||||
/extensions\//i,
|
|
||||||
|
|
||||||
// Locally saved copies
|
|
||||||
/file:\/\//i,
|
|
||||||
|
|
||||||
// Safari extensions.
|
|
||||||
/safari-web-extension:/i,
|
|
||||||
/safari-extension:/i,
|
|
||||||
],
|
|
||||||
integrations: [Sentry.browserTracingIntegration({ router })],
|
|
||||||
ignoreErrors: [
|
|
||||||
'ResizeObserver loop completed with undelivered notifications',
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use(VueDOMPurifyHTML, domPurifyConfig);
|
app.use(VueDOMPurifyHTML, domPurifyConfig);
|
||||||
app.use(WootUiKit);
|
app.use(WootUiKit);
|
||||||
app.use(
|
app.use(
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { createApp } from 'vue';
|
|||||||
import { createI18n } from 'vue-i18n';
|
import { createI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import i18nMessages from 'dashboard/i18n';
|
import i18nMessages from 'dashboard/i18n';
|
||||||
import * as Sentry from '@sentry/vue';
|
|
||||||
import {
|
import {
|
||||||
initializeAnalyticsEvents,
|
initializeAnalyticsEvents,
|
||||||
initializeChatwootEvents,
|
initializeChatwootEvents,
|
||||||
@@ -33,30 +32,6 @@ app.use(router);
|
|||||||
// Vue.prototype.$emitter = emitter;
|
// Vue.prototype.$emitter = emitter;
|
||||||
app.component('fluent-icon', FluentIcon);
|
app.component('fluent-icon', FluentIcon);
|
||||||
|
|
||||||
if (window.errorLoggingConfig) {
|
|
||||||
Sentry.init({
|
|
||||||
app,
|
|
||||||
dsn: window.errorLoggingConfig,
|
|
||||||
denyUrls: [
|
|
||||||
// Chrome extensions
|
|
||||||
/^chrome:\/\//i,
|
|
||||||
/chrome-extension:/i,
|
|
||||||
/extensions\//i,
|
|
||||||
|
|
||||||
// Locally saved copies
|
|
||||||
/file:\/\//i,
|
|
||||||
|
|
||||||
// Safari extensions.
|
|
||||||
/safari-web-extension:/i,
|
|
||||||
/safari-extension:/i,
|
|
||||||
],
|
|
||||||
integrations: [Sentry.browserTracingIntegration({ router })],
|
|
||||||
ignoreErrors: [
|
|
||||||
'ResizeObserver loop completed with undelivered notifications',
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeChatwootEvents();
|
initializeChatwootEvents();
|
||||||
initializeAnalyticsEvents();
|
initializeAnalyticsEvents();
|
||||||
initalizeRouter();
|
initalizeRouter();
|
||||||
|
|||||||
10
app/javascript/shared/helpers/ExceptionTracker.js
Normal file
10
app/javascript/shared/helpers/ExceptionTracker.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// Community Edition exception compatibility surface.
|
||||||
|
// Intentionally local-only: callers may report a failure without creating
|
||||||
|
// network traffic or serializing user/account payloads.
|
||||||
|
const ExceptionTracker = {
|
||||||
|
setContext() {},
|
||||||
|
captureException() {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ExceptionTracker;
|
||||||
|
export { ExceptionTracker };
|
||||||
19
app/javascript/shared/helpers/specs/ExceptionTracker.spec.js
Normal file
19
app/javascript/shared/helpers/specs/ExceptionTracker.spec.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import ExceptionTracker from '../ExceptionTracker';
|
||||||
|
|
||||||
|
describe('ExceptionTracker', () => {
|
||||||
|
it('accepts context and exceptions without retaining or serializing payloads', () => {
|
||||||
|
const context = { accountId: 42, token: '[REDACTED]' };
|
||||||
|
const exception = new Error('local-only failure');
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
ExceptionTracker.setContext('operation', context);
|
||||||
|
ExceptionTracker.captureException(exception);
|
||||||
|
}).not.toThrow();
|
||||||
|
|
||||||
|
expect(Object.keys(ExceptionTracker)).toEqual([
|
||||||
|
'setContext',
|
||||||
|
'captureException',
|
||||||
|
]);
|
||||||
|
expect(JSON.stringify(ExceptionTracker)).toBe('{}');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,8 +2,6 @@ class Conversations::UnreadCounts::FilteredCountInstrumentation
|
|||||||
# Centralizes the rollout-critical filtered unread count signals:
|
# Centralizes the rollout-critical filtered unread count signals:
|
||||||
# API response duration, counter duration, snapshot build duration, snapshot state distribution,
|
# API response duration, counter duration, snapshot build duration, snapshot state distribution,
|
||||||
# refresh claim rate, build lock acquisition rate, and invalidation/version bump rate.
|
# refresh claim rate, build lock acquisition rate, and invalidation/version bump rate.
|
||||||
EVENT_NAME = 'FilteredUnreadCounts'.freeze
|
|
||||||
METRIC_PREFIX = 'Custom/Conversations/UnreadCounts/Filtered'.freeze
|
|
||||||
SUMMARY_KEY = :filtered_unread_counts_request_summary
|
SUMMARY_KEY = :filtered_unread_counts_request_summary
|
||||||
AGGREGATED_INCREMENT_OPERATIONS = %i[snapshot_state refresh_claim build_lock].freeze
|
AGGREGATED_INCREMENT_OPERATIONS = %i[snapshot_state refresh_claim build_lock].freeze
|
||||||
SNAPSHOT_STATUSES = %i[fresh stale missing expired].freeze
|
SNAPSHOT_STATUSES = %i[fresh stale missing expired].freeze
|
||||||
@@ -65,17 +63,6 @@ class Conversations::UnreadCounts::FilteredCountInstrumentation
|
|||||||
|
|
||||||
def increment(operation, attributes = {})
|
def increment(operation, attributes = {})
|
||||||
record_increment_summary(operation, attributes) if aggregated_increment?(operation)
|
record_increment_summary(operation, attributes) if aggregated_increment?(operation)
|
||||||
record_event(operation, attributes) unless aggregated_increment?(operation)
|
|
||||||
record_metric("#{metric_name(operation)}/count", 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
def record_event(operation, attributes = {})
|
|
||||||
agent = new_relic_agent
|
|
||||||
return unless agent.respond_to?(:record_custom_event)
|
|
||||||
|
|
||||||
agent.record_custom_event(EVENT_NAME, sanitized_attributes(attributes.merge(operation: operation)))
|
|
||||||
rescue StandardError
|
|
||||||
nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -83,28 +70,12 @@ class Conversations::UnreadCounts::FilteredCountInstrumentation
|
|||||||
def record_observation(operation, attributes, started_at, status:)
|
def record_observation(operation, attributes, started_at, status:)
|
||||||
duration_ms = elapsed_ms_since(started_at)
|
duration_ms = elapsed_ms_since(started_at)
|
||||||
record_observation_summary(operation, attributes, status: status)
|
record_observation_summary(operation, attributes, status: status)
|
||||||
record_metric("#{metric_name(operation)}/duration_ms", duration_ms)
|
duration_ms
|
||||||
end
|
end
|
||||||
|
|
||||||
def record_request_summary(summary, status, started_at)
|
def record_request_summary(summary, status, started_at)
|
||||||
duration_ms = elapsed_ms_since(started_at)
|
|
||||||
summary[:status] = status
|
summary[:status] = status
|
||||||
summary[:duration_ms] = duration_ms
|
summary[:duration_ms] = elapsed_ms_since(started_at)
|
||||||
record_metric("#{metric_name(:api_response)}/duration_ms", duration_ms)
|
|
||||||
record_event(:request_summary, summary)
|
|
||||||
end
|
|
||||||
|
|
||||||
def record_metric(name, value)
|
|
||||||
agent = new_relic_agent
|
|
||||||
return unless agent.respond_to?(:record_metric)
|
|
||||||
|
|
||||||
agent.record_metric(name, value)
|
|
||||||
rescue StandardError
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def metric_name(operation)
|
|
||||||
"#{METRIC_PREFIX}/#{operation}"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def request_summary(account_id)
|
def request_summary(account_id)
|
||||||
@@ -160,17 +131,6 @@ class Conversations::UnreadCounts::FilteredCountInstrumentation
|
|||||||
current_summary[key] = current_summary.fetch(key, 0) + 1
|
current_summary[key] = current_summary.fetch(key, 0) + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
def sanitized_attributes(attributes)
|
|
||||||
attributes.compact.transform_values do |value|
|
|
||||||
case value
|
|
||||||
when String, Integer, Float, TrueClass, FalseClass
|
|
||||||
value
|
|
||||||
else
|
|
||||||
value.to_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def elapsed_ms_since(started_at)
|
def elapsed_ms_since(started_at)
|
||||||
((monotonic_time - started_at) * 1000).round(2)
|
((monotonic_time - started_at) * 1000).round(2)
|
||||||
end
|
end
|
||||||
@@ -178,11 +138,5 @@ class Conversations::UnreadCounts::FilteredCountInstrumentation
|
|||||||
def monotonic_time
|
def monotonic_time
|
||||||
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
||||||
end
|
end
|
||||||
|
|
||||||
def new_relic_agent
|
|
||||||
return unless defined?(::NewRelic::Agent)
|
|
||||||
|
|
||||||
::NewRelic::Agent
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -62,7 +62,6 @@
|
|||||||
window.browserConfig = {
|
window.browserConfig = {
|
||||||
browser_name: '<%= browser.name %>',
|
browser_name: '<%= browser.name %>',
|
||||||
}
|
}
|
||||||
window.errorLoggingConfig = '<%= ENV.fetch('SENTRY_FRONTEND_DSN', '') || ENV.fetch('SENTRY_DSN', '') %>'
|
|
||||||
</script>
|
</script>
|
||||||
<%= vite_client_tag %>
|
<%= vite_client_tag %>
|
||||||
<%= vite_javascript_tag @application_pack %>
|
<%= vite_javascript_tag @application_pack %>
|
||||||
|
|||||||
@@ -8,24 +8,8 @@ require 'rails/all'
|
|||||||
# you've limited to :test, :development, or :production.
|
# you've limited to :test, :development, or :production.
|
||||||
Bundler.require(*Rails.groups)
|
Bundler.require(*Rails.groups)
|
||||||
|
|
||||||
## Load the specific APM agent
|
## Load application dependencies
|
||||||
# We rely on DOTENV to load the environment variables
|
|
||||||
# We need these environment variables to load the specific APM agent
|
|
||||||
Dotenv::Rails.load
|
Dotenv::Rails.load
|
||||||
require 'datadog' if ENV.fetch('DD_TRACE_AGENT_URL', false).present?
|
|
||||||
require 'elastic-apm' if ENV.fetch('ELASTIC_APM_SECRET_TOKEN', false).present?
|
|
||||||
require 'scout_apm' if ENV.fetch('SCOUT_KEY', false).present?
|
|
||||||
|
|
||||||
if ENV.fetch('NEW_RELIC_LICENSE_KEY', false).present?
|
|
||||||
require 'newrelic-sidekiq-metrics'
|
|
||||||
require 'newrelic_rpm'
|
|
||||||
end
|
|
||||||
|
|
||||||
if ENV.fetch('SENTRY_DSN', false).present?
|
|
||||||
require 'sentry-ruby'
|
|
||||||
require 'sentry-rails'
|
|
||||||
require 'sentry-sidekiq'
|
|
||||||
end
|
|
||||||
|
|
||||||
# heroku autoscaling
|
# heroku autoscaling
|
||||||
if ENV.fetch('JUDOSCALE_URL', false).present?
|
if ENV.fetch('JUDOSCALE_URL', false).present?
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
# Disabled by default to prevent the agent starting up when not required
|
|
||||||
# To enable, set your ELASTIC_APM_SERVER_URL and ELASTIC_APM_SECRET_TOKEN in your .env
|
|
||||||
# Additional configuration options can be set below, as per the docs: https://www.elastic.co/guide/en/apm/agent/ruby/current/configuration.html#configuration
|
|
||||||
|
|
||||||
enabled: <%= ENV.fetch('ELASTIC_APM_SECRET_TOKEN', false).present? %>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
if ENV['DD_TRACE_AGENT_URL'].present?
|
|
||||||
Datadog.configure do |c|
|
|
||||||
# Instrumentation
|
|
||||||
c.tracing.instrument :rails
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
if ENV['SENTRY_DSN'].present?
|
|
||||||
Sentry.init do |config|
|
|
||||||
config.dsn = ENV['SENTRY_DSN']
|
|
||||||
config.enabled_environments = %w[staging production]
|
|
||||||
|
|
||||||
# To activate performance monitoring, set one of these options.
|
|
||||||
# We recommend adjusting the value in production:
|
|
||||||
config.traces_sample_rate = 0.1 if ENV['ENABLE_SENTRY_TRANSACTIONS']
|
|
||||||
|
|
||||||
config.excluded_exceptions += ['Rack::Timeout::RequestTimeoutException', 'MutexApplicationJob::LockAcquisitionError']
|
|
||||||
|
|
||||||
# to track post data in sentry
|
|
||||||
config.send_default_pii = true unless ENV['DISABLE_SENTRY_PII']
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
#
|
|
||||||
# This file configures the New Relic Agent. New Relic monitors Ruby, Java,
|
|
||||||
# .NET, PHP, Python, Node, and Go applications with deep visibility and low
|
|
||||||
# overhead. For more information, visit www.newrelic.com.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# For full documentation of agent configuration options, please refer to
|
|
||||||
# https://docs.newrelic.com/docs/agents/ruby-agent/installation-configuration/ruby-agent-configuration
|
|
||||||
|
|
||||||
common: &default_settings
|
|
||||||
# Required license key associated with your New Relic account.
|
|
||||||
license_key: <%= ENV['NEW_RELIC_LICENSE_KEY'] %>
|
|
||||||
|
|
||||||
# Your application name. Renaming here affects where data displays in New
|
|
||||||
# Relic. For more details, see https://docs.newrelic.com/docs/apm/new-relic-apm/maintenance/renaming-applications
|
|
||||||
app_name: <%= ENV.fetch('NEW_RELIC_APP_NAME', 'Chatwoot') %>
|
|
||||||
|
|
||||||
distributed_tracing:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
# To disable the agent regardless of other settings, uncomment the following:
|
|
||||||
agent_enabled: <%= ENV['NEW_RELIC_LICENSE_KEY'].present? && ENV.fetch('NEW_RELIC_AGENT_ENABLED', true) %>
|
|
||||||
|
|
||||||
# Logging level for log/newrelic_agent.log
|
|
||||||
log_level: <%= ENV.fetch('NEW_RELIC_LOG_LEVEL', 'info') %>
|
|
||||||
|
|
||||||
application_logging:
|
|
||||||
# If `true`, all logging-related features for the agent can be enabled or disabled
|
|
||||||
# independently. If `false`, all logging-related features are disabled.
|
|
||||||
enabled: <%= ENV.fetch('NEW_RELIC_APPLICATION_LOGGING_ENABLED', false) %>
|
|
||||||
forwarding:
|
|
||||||
# If `true`, the agent captures log records emitted by this application
|
|
||||||
enabled: <%= ENV.fetch('NEW_RELIC_APPLICATION_LOGGING_FORWARDING_ENABLED', true) == "false" ? false : true %>
|
|
||||||
# Defines the maximum number of log records to buffer in memory at a time.
|
|
||||||
max_samples_stored: 30000
|
|
||||||
metrics:
|
|
||||||
# If `true`, the agent captures metrics related to logging for this application.
|
|
||||||
enabled: true
|
|
||||||
local_decorating:
|
|
||||||
# If `true`, the agent decorates logs with metadata to link to entities, hosts, traces, and spans.
|
|
||||||
# This requires a log forwarder to send your log files to New Relic.
|
|
||||||
# This should not be used when forwarding is enabled.
|
|
||||||
enabled: <%= ENV.fetch('NEW_RELIC_APPLICATION_LOGGING_DECORATING_ENABLED', false) %>
|
|
||||||
|
|
||||||
|
|
||||||
# Environment-specific settings are in this section.
|
|
||||||
# RAILS_ENV or RACK_ENV (as appropriate) is used to determine the environment.
|
|
||||||
# If your application has other named environments, configure them here.
|
|
||||||
development:
|
|
||||||
<<: *default_settings
|
|
||||||
app_name: <%= ENV.fetch('NEW_RELIC_APP_NAME', 'Chatwoot') %> (Development)
|
|
||||||
|
|
||||||
test:
|
|
||||||
<<: *default_settings
|
|
||||||
# It doesn't make sense to report to New Relic from automated test runs.
|
|
||||||
monitor_mode: false
|
|
||||||
|
|
||||||
staging:
|
|
||||||
<<: *default_settings
|
|
||||||
app_name: <%= ENV.fetch('NEW_RELIC_APP_NAME', 'Chatwoot') %> (Staging)
|
|
||||||
|
|
||||||
production:
|
|
||||||
<<: *default_settings
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
common: &defaults
|
|
||||||
name: <%= ENV.fetch('SCOUT_NAME', '') %>
|
|
||||||
key: <%= ENV.fetch('SCOUT_KEY', '') %>
|
|
||||||
monitor: <%= ENV['SCOUT_KEY'].present? && ENV.fetch('SCOUT_MONITOR', true) %>
|
|
||||||
|
|
||||||
test:
|
|
||||||
monitor: <%= ENV['SCOUT_KEY'].present? && ENV.fetch('SCOUT_MONITOR', true) %>
|
|
||||||
|
|
||||||
production:
|
|
||||||
<<: *defaults
|
|
||||||
@@ -1,32 +1,23 @@
|
|||||||
###############
|
###############
|
||||||
# One library to capture_exception and send to the specific service.
|
# Local-only exception compatibility surface.
|
||||||
# # e as exception, u for user and a for account (user and account are optional)
|
# The optional user/account arguments remain accepted for callsite compatibility,
|
||||||
# Usage: ChatwootExceptionTracker(e, user: u, account: a).capture_exception
|
# but are intentionally not retained, serialized, or sent anywhere.
|
||||||
############
|
############
|
||||||
|
|
||||||
class ChatwootExceptionTracker
|
class ChatwootExceptionTracker
|
||||||
def initialize(exception, user: nil, account: nil)
|
def initialize(exception, user: nil, account: nil)
|
||||||
@exception = exception
|
@exception = exception
|
||||||
@user = user
|
discard_optional_context(user)
|
||||||
@account = account
|
discard_optional_context(account)
|
||||||
end
|
end
|
||||||
|
|
||||||
def capture_exception
|
def capture_exception
|
||||||
capture_exception_with_sentry if ENV['SENTRY_DSN'].present?
|
|
||||||
Rails.logger.error @exception
|
Rails.logger.error @exception
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def capture_exception_with_sentry
|
def discard_optional_context(_value)
|
||||||
Sentry.with_scope do |scope|
|
nil
|
||||||
if @account.present?
|
|
||||||
scope.set_context('account', { id: @account.id, name: @account.name })
|
|
||||||
scope.set_tags(account_id: @account.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
scope.set_user(id: @user.id, email: @user.email) if @user.is_a?(User)
|
|
||||||
Sentry.capture_exception(@exception)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
# Download Report Rake Tasks
|
# Download Report Rake Tasks
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# POSTGRES_STATEMENT_TIMEOUT=600s NEW_RELIC_AGENT_ENABLED=false bundle exec rake download_report:agent
|
# POSTGRES_STATEMENT_TIMEOUT=600s bundle exec rake download_report:agent
|
||||||
# POSTGRES_STATEMENT_TIMEOUT=600s NEW_RELIC_AGENT_ENABLED=false bundle exec rake download_report:inbox
|
# POSTGRES_STATEMENT_TIMEOUT=600s bundle exec rake download_report:inbox
|
||||||
# POSTGRES_STATEMENT_TIMEOUT=600s NEW_RELIC_AGENT_ENABLED=false bundle exec rake download_report:label
|
# POSTGRES_STATEMENT_TIMEOUT=600s bundle exec rake download_report:label
|
||||||
#
|
#
|
||||||
# The task will prompt for:
|
# The task will prompt for:
|
||||||
# - Account ID
|
# - Account ID
|
||||||
|
|||||||
@@ -47,7 +47,6 @@
|
|||||||
"@rails/actioncable": "6.1.3",
|
"@rails/actioncable": "6.1.3",
|
||||||
"@rails/ujs": "^7.1.400",
|
"@rails/ujs": "^7.1.400",
|
||||||
"@scmmishra/pico-search": "0.6.0",
|
"@scmmishra/pico-search": "0.6.0",
|
||||||
"@sentry/vue": "^8.55.0",
|
|
||||||
"@sindresorhus/slugify": "2.2.1",
|
"@sindresorhus/slugify": "2.2.1",
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"@tanstack/vue-table": "^8.20.5",
|
"@tanstack/vue-table": "^8.20.5",
|
||||||
|
|||||||
76
pnpm-lock.yaml
generated
76
pnpm-lock.yaml
generated
@@ -63,9 +63,6 @@ importers:
|
|||||||
'@scmmishra/pico-search':
|
'@scmmishra/pico-search':
|
||||||
specifier: 0.6.0
|
specifier: 0.6.0
|
||||||
version: 0.6.0
|
version: 0.6.0
|
||||||
'@sentry/vue':
|
|
||||||
specifier: ^8.55.0
|
|
||||||
version: 8.55.0(pinia@3.0.4(typescript@5.6.2)(vue@3.5.12(typescript@5.6.2)))(vue@3.5.12(typescript@5.6.2))
|
|
||||||
'@sindresorhus/slugify':
|
'@sindresorhus/slugify':
|
||||||
specifier: 2.2.1
|
specifier: 2.2.1
|
||||||
version: 2.2.1
|
version: 2.2.1
|
||||||
@@ -1264,40 +1261,6 @@ packages:
|
|||||||
'@scmmishra/pico-search@0.6.0':
|
'@scmmishra/pico-search@0.6.0':
|
||||||
resolution: {integrity: sha512-1zC2cAwPWuv38VEh0It90fdUWkvX75OwBUjgTj+d5LTltARnf3ydbpcN2Ucl0aATBMmaNqPMcVvT25IOCAqCEA==}
|
resolution: {integrity: sha512-1zC2cAwPWuv38VEh0It90fdUWkvX75OwBUjgTj+d5LTltARnf3ydbpcN2Ucl0aATBMmaNqPMcVvT25IOCAqCEA==}
|
||||||
|
|
||||||
'@sentry-internal/browser-utils@8.55.0':
|
|
||||||
resolution: {integrity: sha512-ROgqtQfpH/82AQIpESPqPQe0UyWywKJsmVIqi3c5Fh+zkds5LUxnssTj3yNd1x+kxaPDVB023jAP+3ibNgeNDw==}
|
|
||||||
engines: {node: '>=14.18'}
|
|
||||||
|
|
||||||
'@sentry-internal/feedback@8.55.0':
|
|
||||||
resolution: {integrity: sha512-cP3BD/Q6pquVQ+YL+rwCnorKuTXiS9KXW8HNKu4nmmBAyf7urjs+F6Hr1k9MXP5yQ8W3yK7jRWd09Yu6DHWOiw==}
|
|
||||||
engines: {node: '>=14.18'}
|
|
||||||
|
|
||||||
'@sentry-internal/replay-canvas@8.55.0':
|
|
||||||
resolution: {integrity: sha512-nIkfgRWk1091zHdu4NbocQsxZF1rv1f7bbp3tTIlZYbrH62XVZosx5iHAuZG0Zc48AETLE7K4AX9VGjvQj8i9w==}
|
|
||||||
engines: {node: '>=14.18'}
|
|
||||||
|
|
||||||
'@sentry-internal/replay@8.55.0':
|
|
||||||
resolution: {integrity: sha512-roCDEGkORwolxBn8xAKedybY+Jlefq3xYmgN2fr3BTnsXjSYOPC7D1/mYqINBat99nDtvgFvNfRcZPiwwZ1hSw==}
|
|
||||||
engines: {node: '>=14.18'}
|
|
||||||
|
|
||||||
'@sentry/browser@8.55.0':
|
|
||||||
resolution: {integrity: sha512-1A31mCEWCjaMxJt6qGUK+aDnLDcK6AwLAZnqpSchNysGni1pSn1RWSmk9TBF8qyTds5FH8B31H480uxMPUJ7Cw==}
|
|
||||||
engines: {node: '>=14.18'}
|
|
||||||
|
|
||||||
'@sentry/core@8.55.0':
|
|
||||||
resolution: {integrity: sha512-6g7jpbefjHYs821Z+EBJ8r4Z7LT5h80YSWRJaylGS4nW5W5Z2KXzpdnyFarv37O7QjauzVC2E+PABmpkw5/JGA==}
|
|
||||||
engines: {node: '>=14.18'}
|
|
||||||
|
|
||||||
'@sentry/vue@8.55.0':
|
|
||||||
resolution: {integrity: sha512-J6lcpzL39snV/spoGpwyk5Rp1wSFxOV4qV1NhQ9OlLHORVBp/Xpw7cEA0oKqG2w1wVtCV+gC5Jjf9HTmYiHQOQ==}
|
|
||||||
engines: {node: '>=14.18'}
|
|
||||||
peerDependencies:
|
|
||||||
pinia: 2.x || 3.x
|
|
||||||
vue: 2.x || 3.x
|
|
||||||
peerDependenciesMeta:
|
|
||||||
pinia:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@sindresorhus/slugify@2.2.1':
|
'@sindresorhus/slugify@2.2.1':
|
||||||
resolution: {integrity: sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==}
|
resolution: {integrity: sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -4972,7 +4935,6 @@ snapshots:
|
|||||||
|
|
||||||
'@alloc/quick-lru@5.2.0': {}
|
'@alloc/quick-lru@5.2.0': {}
|
||||||
|
|
||||||
|
|
||||||
'@ampproject/remapping@2.3.0':
|
'@ampproject/remapping@2.3.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/gen-mapping': 0.3.8
|
'@jridgewell/gen-mapping': 0.3.8
|
||||||
@@ -5860,42 +5822,6 @@ snapshots:
|
|||||||
|
|
||||||
'@scmmishra/pico-search@0.6.0': {}
|
'@scmmishra/pico-search@0.6.0': {}
|
||||||
|
|
||||||
'@sentry-internal/browser-utils@8.55.0':
|
|
||||||
dependencies:
|
|
||||||
'@sentry/core': 8.55.0
|
|
||||||
|
|
||||||
'@sentry-internal/feedback@8.55.0':
|
|
||||||
dependencies:
|
|
||||||
'@sentry/core': 8.55.0
|
|
||||||
|
|
||||||
'@sentry-internal/replay-canvas@8.55.0':
|
|
||||||
dependencies:
|
|
||||||
'@sentry-internal/replay': 8.55.0
|
|
||||||
'@sentry/core': 8.55.0
|
|
||||||
|
|
||||||
'@sentry-internal/replay@8.55.0':
|
|
||||||
dependencies:
|
|
||||||
'@sentry-internal/browser-utils': 8.55.0
|
|
||||||
'@sentry/core': 8.55.0
|
|
||||||
|
|
||||||
'@sentry/browser@8.55.0':
|
|
||||||
dependencies:
|
|
||||||
'@sentry-internal/browser-utils': 8.55.0
|
|
||||||
'@sentry-internal/feedback': 8.55.0
|
|
||||||
'@sentry-internal/replay': 8.55.0
|
|
||||||
'@sentry-internal/replay-canvas': 8.55.0
|
|
||||||
'@sentry/core': 8.55.0
|
|
||||||
|
|
||||||
'@sentry/core@8.55.0': {}
|
|
||||||
|
|
||||||
'@sentry/vue@8.55.0(pinia@3.0.4(typescript@5.6.2)(vue@3.5.12(typescript@5.6.2)))(vue@3.5.12(typescript@5.6.2))':
|
|
||||||
dependencies:
|
|
||||||
'@sentry/browser': 8.55.0
|
|
||||||
'@sentry/core': 8.55.0
|
|
||||||
vue: 3.5.12(typescript@5.6.2)
|
|
||||||
optionalDependencies:
|
|
||||||
pinia: 3.0.4(typescript@5.6.2)(vue@3.5.12(typescript@5.6.2))
|
|
||||||
|
|
||||||
'@sindresorhus/slugify@2.2.1':
|
'@sindresorhus/slugify@2.2.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sindresorhus/transliterate': 1.6.0
|
'@sindresorhus/transliterate': 1.6.0
|
||||||
@@ -5963,7 +5889,6 @@ snapshots:
|
|||||||
|
|
||||||
'@types/web-bluetooth@0.0.20': {}
|
'@types/web-bluetooth@0.0.20': {}
|
||||||
|
|
||||||
|
|
||||||
'@ungap/structured-clone@1.2.0': {}
|
'@ungap/structured-clone@1.2.0': {}
|
||||||
|
|
||||||
'@videojs/http-streaming@2.15.1(video.js@7.21.1)':
|
'@videojs/http-streaming@2.15.1(video.js@7.21.1)':
|
||||||
@@ -9884,7 +9809,6 @@ snapshots:
|
|||||||
|
|
||||||
wavesurfer.js@7.8.6: {}
|
wavesurfer.js@7.8.6: {}
|
||||||
|
|
||||||
|
|
||||||
webidl-conversions@7.0.0: {}
|
webidl-conversions@7.0.0: {}
|
||||||
|
|
||||||
webidl-conversions@8.0.0: {}
|
webidl-conversions@8.0.0: {}
|
||||||
|
|||||||
@@ -6,18 +6,28 @@ describe '/app/login', type: :request do
|
|||||||
get '/app/login'
|
get '/app/login'
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not serialize the cloud analytics token' do
|
it 'does not serialize the cloud analytics token' do
|
||||||
allow(GlobalConfig).to receive(:get).and_return(
|
allow(GlobalConfig).to receive(:get).and_return(
|
||||||
{ 'CLOUD_ANALYTICS_TOKEN' => 'analytics-test-token' }
|
{ 'CLOUD_ANALYTICS_TOKEN' => '[REDACTED]' }
|
||||||
)
|
)
|
||||||
|
|
||||||
get '/app/login'
|
get '/app/login'
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
expect(response.body).not_to include('analyticsConfig')
|
expect(response.body).not_to include('analyticsConfig')
|
||||||
expect(response.body).not_to include('analytics-test-token')
|
expect(response.body).not_to include('[REDACTED]')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'does not serialize the frontend error reporting DSN' do
|
||||||
|
with_modified_env SENTRY_FRONTEND_DSN: 'https://sentry.invalid/frontend', SENTRY_DSN: 'https://sentry.invalid/backend' do
|
||||||
|
get '/app/login'
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(response.body).not_to include('errorLoggingConfig')
|
||||||
|
expect(response.body).not_to include('sentry.invalid')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with DEFAULT_LOCALE' do
|
context 'with DEFAULT_LOCALE' do
|
||||||
|
|||||||
@@ -1,26 +1,22 @@
|
|||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
# explicitly requiring since we are loading apms conditionally in application.rb
|
|
||||||
require 'sentry-ruby'
|
|
||||||
|
|
||||||
describe ChatwootExceptionTracker do
|
describe ChatwootExceptionTracker do
|
||||||
it 'use rails logger if no tracker is configured' do
|
it 'writes the exception to the Rails logger' do
|
||||||
expect(Rails.logger).to receive(:error).with('random')
|
exception = StandardError.new('local failure')
|
||||||
described_class.new('random').capture_exception
|
|
||||||
|
expect(Rails.logger).to receive(:error).with(exception)
|
||||||
|
|
||||||
|
described_class.new(exception).capture_exception
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with sentry DSN' do
|
it 'stays local when a Sentry DSN is present' do
|
||||||
before do
|
exception = StandardError.new('private failure')
|
||||||
# since sentry is not initated in test, we need to do it manually
|
user = build(:user, email: 'person@example.invalid', name: 'Private User')
|
||||||
Sentry.init do |config|
|
account = build(:account, name: 'Private Account')
|
||||||
config.dsn = 'test'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'will call sentry capture exception' do
|
with_modified_env SENTRY_DSN: 'https://sentry.invalid/project' do
|
||||||
with_modified_env SENTRY_DSN: 'random dsn' do
|
expect(Rails.logger).to receive(:error).with(exception)
|
||||||
expect(Sentry).to receive(:capture_exception).with('random')
|
described_class.new(exception, user: user, account: account).capture_exception
|
||||||
described_class.new('random').capture_exception
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,119 +1,40 @@
|
|||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe Conversations::UnreadCounts::FilteredCountInstrumentation do
|
RSpec.describe Conversations::UnreadCounts::FilteredCountInstrumentation do
|
||||||
let(:new_relic_agent) do
|
|
||||||
Class.new do
|
|
||||||
def self.record_custom_event(*) end
|
|
||||||
def self.record_metric(*) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
before do
|
|
||||||
stub_const('NewRelic::Agent', new_relic_agent)
|
|
||||||
allow(new_relic_agent).to receive(:record_custom_event)
|
|
||||||
allow(new_relic_agent).to receive(:record_metric)
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '.observe' do
|
describe '.observe' do
|
||||||
it 'records duration metrics without custom events around successful operations' do
|
it 'returns the operation result without remote instrumentation' do
|
||||||
result = described_class.observe(:counter_perform, account_id: 1, snapshot_scope: :built_in_filter) { 'ok' }
|
result = described_class.observe(:counter_perform, account_id: 1, snapshot_scope: :built_in_filter) { 'ok' }
|
||||||
|
|
||||||
expect(result).to eq('ok')
|
expect(result).to eq('ok')
|
||||||
expect(new_relic_agent).not_to have_received(:record_custom_event)
|
|
||||||
expect(new_relic_agent).to have_received(:record_metric).with(
|
|
||||||
'Custom/Conversations/UnreadCounts/Filtered/counter_perform/duration_ms',
|
|
||||||
kind_of(Float)
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'records failed operations and re-raises the original error' do
|
it 're-raises failed operations without remote instrumentation' do
|
||||||
error = StandardError.new('boom')
|
error = StandardError.new('boom')
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
described_class.observe(:snapshot_build, account_id: 1) { raise error }
|
described_class.observe(:snapshot_build, account_id: 1) { raise error }
|
||||||
end.to raise_error(error)
|
end.to raise_error(error)
|
||||||
expect(new_relic_agent).not_to have_received(:record_custom_event)
|
|
||||||
expect(new_relic_agent).to have_received(:record_metric).with(
|
|
||||||
'Custom/Conversations/UnreadCounts/Filtered/snapshot_build/duration_ms',
|
|
||||||
kind_of(Float)
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.increment' do
|
describe '.increment' do
|
||||||
it 'records count metrics without custom events for aggregated read-path operations' do
|
it 'keeps local summary counters without requiring an APM agent' do
|
||||||
described_class.increment(:snapshot_state, account_id: 1, snapshot_status: :fresh)
|
expect do
|
||||||
|
described_class.increment(:snapshot_state, account_id: 1, snapshot_status: :fresh)
|
||||||
expect(new_relic_agent).not_to have_received(:record_custom_event)
|
described_class.increment(:invalidation, account_id: 1, invalidation_scope: :conversation)
|
||||||
expect(new_relic_agent).to have_received(:record_metric).with(
|
end.not_to raise_error
|
||||||
'Custom/Conversations/UnreadCounts/Filtered/snapshot_state/count',
|
|
||||||
1
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'keeps custom events for invalidation signals' do
|
|
||||||
described_class.increment(:invalidation, account_id: 1, invalidation_scope: :conversation)
|
|
||||||
|
|
||||||
expect(new_relic_agent).to have_received(:record_custom_event).with(
|
|
||||||
'FilteredUnreadCounts',
|
|
||||||
hash_including(
|
|
||||||
account_id: 1,
|
|
||||||
invalidation_scope: 'conversation',
|
|
||||||
operation: 'invalidation'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
expect(new_relic_agent).to have_received(:record_metric).with(
|
|
||||||
'Custom/Conversations/UnreadCounts/Filtered/invalidation/count',
|
|
||||||
1
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not raise when New Relic is unavailable' do
|
|
||||||
allow(described_class).to receive(:new_relic_agent).and_return(nil)
|
|
||||||
|
|
||||||
expect { described_class.increment(:snapshot_state, account_id: 1) }.not_to raise_error
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.summarize_request' do
|
describe '.summarize_request' do
|
||||||
it 'records one custom event with aggregated request counters' do
|
it 'returns the wrapped result while keeping request summaries local' do
|
||||||
result = described_class.summarize_request(account_id: 1) do
|
result = described_class.summarize_request(account_id: 1) do
|
||||||
described_class.increment(:snapshot_state, account_id: 1, snapshot_scope: :built_in_filter, snapshot_status: :fresh)
|
|
||||||
described_class.increment(:snapshot_state, account_id: 1, snapshot_scope: :filter, snapshot_status: :missing)
|
described_class.increment(:snapshot_state, account_id: 1, snapshot_scope: :filter, snapshot_status: :missing)
|
||||||
described_class.increment(:refresh_claim, account_id: 1, snapshot_scope: :filter, claimed: true)
|
|
||||||
described_class.increment(:refresh_claim, account_id: 1, snapshot_scope: :filter, claimed: false)
|
|
||||||
described_class.increment(:build_lock, account_id: 1, snapshot_scope: :filter, acquired: true)
|
|
||||||
described_class.observe(:snapshot_build, account_id: 1, snapshot_scope: :filter) { 'built' }
|
described_class.observe(:snapshot_build, account_id: 1, snapshot_scope: :filter) { 'built' }
|
||||||
|
|
||||||
'ok'
|
'ok'
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(result).to eq('ok')
|
expect(result).to eq('ok')
|
||||||
expect(new_relic_agent).to have_received(:record_custom_event).once.with(
|
|
||||||
'FilteredUnreadCounts',
|
|
||||||
hash_including(
|
|
||||||
account_id: 1,
|
|
||||||
build_lock_acquired_count: 1,
|
|
||||||
duration_ms: kind_of(Float),
|
|
||||||
filter_build_lock_acquired_count: 1,
|
|
||||||
filter_refresh_claimed_count: 1,
|
|
||||||
filter_refresh_skipped_count: 1,
|
|
||||||
filter_snapshot_build_success_count: 1,
|
|
||||||
filter_snapshot_count: 1,
|
|
||||||
operation: 'request_summary',
|
|
||||||
refresh_claimed_count: 1,
|
|
||||||
refresh_skipped_count: 1,
|
|
||||||
snapshot_build_success_count: 1,
|
|
||||||
snapshot_fresh_count: 1,
|
|
||||||
snapshot_missing_count: 1,
|
|
||||||
snapshot_total_count: 2,
|
|
||||||
status: 'success'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
expect(new_relic_agent).to have_received(:record_metric).with(
|
|
||||||
'Custom/Conversations/UnreadCounts/Filtered/api_response/duration_ms',
|
|
||||||
kind_of(Float)
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'records summary errors and re-raises the original error' do
|
it 'records summary errors and re-raises the original error' do
|
||||||
@@ -122,16 +43,6 @@ RSpec.describe Conversations::UnreadCounts::FilteredCountInstrumentation do
|
|||||||
expect do
|
expect do
|
||||||
described_class.summarize_request(account_id: 1) { raise error }
|
described_class.summarize_request(account_id: 1) { raise error }
|
||||||
end.to raise_error(error)
|
end.to raise_error(error)
|
||||||
|
|
||||||
expect(new_relic_agent).to have_received(:record_custom_event).with(
|
|
||||||
'FilteredUnreadCounts',
|
|
||||||
hash_including(
|
|
||||||
account_id: 1,
|
|
||||||
error_class: 'StandardError',
|
|
||||||
operation: 'request_summary',
|
|
||||||
status: 'error'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user