ci: update oss plugin release process (#5051)

# What this PR does

Related to https://github.com/grafana/irm/pull/137 (**NOTE**: should
only be merged after those changed are back-merged here into
`grafana/oncall`)

## Which issue(s) this PR closes

Also, fixes https://github.com/grafana/oncall/issues/5028

---------

Co-authored-by: Dominik <dominik.broj@grafana.com>
This commit is contained in:
Joey Orlando 2024-09-23 11:56:16 -04:00 committed by GitHub
parent 20d2d5a578
commit e882e9782d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 130 additions and 178 deletions

View file

@ -2,18 +2,10 @@ name: Build, sign, and package plugin
description: Build, sign, and package plugin description: Build, sign, and package plugin
inputs: inputs:
plugin_version_number: plugin_version_number:
description: "The version number of the plugin" description: |
The version number of the plugin. NOTE: this action will chop off the leading "v" to use
it as the official plugin version.
required: true required: true
grafana_access_policy_token:
description: "The Grafana access policy token used to sign the plugin"
required: true
working_directory:
description: "The working directory of the plugin"
required: true
is_enterprise:
description: "Whether the plugin is an enterprise build or not"
required: false
default: "false"
outputs: outputs:
artifact_filename: artifact_filename:
description: "The filename of the plugin artifact" description: "The filename of the plugin artifact"
@ -21,12 +13,24 @@ outputs:
runs: runs:
using: "composite" using: "composite"
steps: steps:
# This will fetch the secret keys from vault and set them as environment variables for subsequent steps
- name: Get Vault secrets
uses: grafana/shared-workflows/actions/get-vault-secrets@main
with:
repo_secrets: |
GRAFANA_ACCESS_POLICY_TOKEN=github_actions:cloud-access-policy-token
- name: Determine official plugin version
id: plugin-version
shell: bash
run: |
# VERY IMPORTANT: chop off the "v".. this tells the oncall plugin that this is an OSS build
PLUGIN_VERSION="$(echo ${{ inputs.plugin_version_number }} | sed 's/^v//')"
echo version="$PLUGIN_VERSION" >> $GITHUB_OUTPUT
- name: Determine artifact filename - name: Determine artifact filename
shell: bash shell: bash
id: artifact-filename id: artifact-filename
# yamllint disable rule:line-length
run: | run: |
echo filename="grafana-oncall${{ inputs.is_enterprise == 'true' && '-ee' || '' }}-app-${{ inputs.plugin_version_number }}.zip" >> $GITHUB_OUTPUT echo filename="grafana-oncall-app-${{ steps.plugin-version.outputs.version }}.zip" >> $GITHUB_OUTPUT
- name: Install Go - name: Install Go
uses: actions/setup-go@v4 uses: actions/setup-go@v4
with: with:
@ -36,16 +40,18 @@ runs:
run: go install github.com/magefile/mage@v1.15.0 run: go install github.com/magefile/mage@v1.15.0
- name: Build, sign, and package plugin - name: Build, sign, and package plugin
shell: bash shell: bash
working-directory: ${{ inputs.working_directory }} working-directory: grafana-plugin
env:
GRAFANA_ACCESS_POLICY_TOKEN: ${{ inputs.grafana_access_policy_token }}
run: | run: |
jq --arg v "${{ inputs.plugin_version_number }}" '.version=$v' package.json > package.new && mv package.new package.json && jq '.version' package.json; jq --arg v "${{ steps.plugin-version.outputs.version }}" '.version=$v' package.json > package.new && \
mv package.new package.json && \
jq '.version' package.json;
pnpm build pnpm build
mage buildAll || true mage buildAll || true
pnpm sign pnpm sign
if [ ! -f dist/MANIFEST.txt ]; then echo "Sign failed, MANIFEST.txt not created, aborting." && exit 1; fi if [ ! -f dist/MANIFEST.txt ]; then echo "Sign failed, MANIFEST.txt not created, aborting." && exit 1; fi
mv dist grafana-oncall-app mv dist grafana-oncall-app
zip -r grafana-oncall-app.zip ./grafana-oncall-app zip -r grafana-oncall-app.zip ./grafana-oncall-app
cp grafana-oncall-app.zip ${{ steps.artifact-filename.outputs.filename }} cp grafana-oncall-app.zip ${{ steps.artifact-filename.outputs.filename }}
# yamllint enable rule:line-length

View file

@ -36,15 +36,12 @@ jobs:
uses: grafana/shared-workflows/actions/get-vault-secrets@main uses: grafana/shared-workflows/actions/get-vault-secrets@main
with: with:
repo_secrets: | repo_secrets: |
GRAFANA_ACCESS_POLICY_TOKEN=github_actions:cloud-access-policy-token
GCS_PLUGIN_PUBLISHER_SERVICE_ACCOUNT_JSON=github_actions:gcs-plugin-publisher GCS_PLUGIN_PUBLISHER_SERVICE_ACCOUNT_JSON=github_actions:gcs-plugin-publisher
- name: Build, sign, and package plugin - name: Build, sign, and package plugin
id: build-sign-and-package-plugin id: build-sign-and-package-plugin
uses: ./.github/actions/build-sign-and-package-plugin uses: ./.github/actions/build-sign-and-package-plugin
with: with:
plugin_version_number: ${{ github.ref_name }} plugin_version_number: ${{ github.ref_name }}
grafana_access_policy_token: ${{ env.GRAFANA_ACCESS_POLICY_TOKEN }}
working_directory: grafana-plugin
- name: Authenticate with GCS - name: Authenticate with GCS
uses: google-github-actions/auth@v2 uses: google-github-actions/auth@v2
with: with:

View file

@ -64,20 +64,20 @@ repos:
- "@grafana/eslint-config@^5.0.0" - "@grafana/eslint-config@^5.0.0"
- eslint-plugin-promise@^6.1.1 - eslint-plugin-promise@^6.1.1
- repo: https://github.com/pre-commit/mirrors-prettier # - repo: https://github.com/pre-commit/mirrors-prettier
rev: "v2.7.1" # rev: "v2.7.1"
hooks: # hooks:
- id: prettier # - id: prettier
name: prettier # name: prettier
types_or: [css, javascript, jsx, ts, tsx] # types_or: [css, javascript, jsx, ts, tsx]
files: ^grafana-plugin/src # files: ^grafana-plugin/src
additional_dependencies: # additional_dependencies:
- prettier@2.8.2 # - prettier@2.8.2
- id: prettier # - id: prettier
name: prettier - json # name: prettier - json
types_or: [json] # types_or: [json]
additional_dependencies: # additional_dependencies:
- prettier@2.8.2 # - prettier@2.8.2
- repo: https://github.com/thibaudcolas/pre-commit-stylelint - repo: https://github.com/thibaudcolas/pre-commit-stylelint
rev: v13.13.1 rev: v13.13.1

View file

@ -1,7 +1,7 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { test, expect, Locator } from '../fixtures'; import { test, expect, Locator } from '../fixtures';
import { MOSCOW_TIMEZONE } from '../utils/constants'; import { isGrafanaVersionGreaterThan, MOSCOW_TIMEZONE } from '../utils/constants';
import { clickButton, generateRandomValue } from '../utils/forms'; import { clickButton, generateRandomValue } from '../utils/forms';
import { setTimezoneInProfile } from '../utils/grafanaProfile'; import { setTimezoneInProfile } from '../utils/grafanaProfile';
import { createOnCallSchedule, getOverrideFormDateInputs } from '../utils/schedule'; import { createOnCallSchedule, getOverrideFormDateInputs } from '../utils/schedule';
@ -12,6 +12,12 @@ test('Default dates in override creation modal are set to today', async ({ admin
const onCallScheduleName = generateRandomValue(); const onCallScheduleName = generateRandomValue();
await createOnCallSchedule(page, onCallScheduleName, userName); await createOnCallSchedule(page, onCallScheduleName, userName);
await page.clock.setFixedTime(new Date().setHours(12, 0, 0, 0));
await page.getByTestId('timezone-select').locator('svg').click();
await (isGrafanaVersionGreaterThan('11.0.0') ? page.getByRole('option') : page.getByLabel('Select option'))
.getByText(/^GMT$/)
.click();
await clickButton({ page, buttonText: 'Add override' }); await clickButton({ page, buttonText: 'Add override' });
const overrideFormDateInputs = await getOverrideFormDateInputs(page); const overrideFormDateInputs = await getOverrideFormDateInputs(page);

View file

@ -22,7 +22,7 @@ test.skip('dates in schedule are correct according to selected current timezone'
* Always set a fixed time of today's date but at 12:00:00 (noon) * Always set a fixed time of today's date but at 12:00:00 (noon)
* *
* This solves the issue here https://github.com/grafana/oncall/issues/4991 * This solves the issue here https://github.com/grafana/oncall/issues/4991
* where we would occasionally see this test flake if it srtated and finished at a different hour * where we would occasionally see this test flake if it started and finished at a different hour
* *
* See playwright docs for more details * See playwright docs for more details
* https://playwright.dev/docs/clock * https://playwright.dev/docs/clock

View file

@ -70,7 +70,7 @@ export const configureUserNotificationSettings = async (page: Page, notifyBy: No
// select our notification type, first check if we have any already defined, if so, click the // select our notification type, first check if we have any already defined, if so, click the
// "Add Notification Step" button // "Add Notification Step" button
const defaultNotificationSettingsSection = getDefaultNotificationSettingsSectionByTestId(page); const defaultNotificationSettingsSection = getDefaultNotificationSettingsSectionByTestId(page);
const addNotificationStepText = 'Add Notification Step'; const addNotificationStepText = 'Add notification step';
if (!(await defaultNotificationSettingsSection.locator(`button >> text=${addNotificationStepText}`).isVisible())) { if (!(await defaultNotificationSettingsSection.locator(`button >> text=${addNotificationStepText}`).isVisible())) {
await clickButton({ await clickButton({

View file

@ -1,6 +1,6 @@
{ {
"name": "grafana-oncall-app", "name": "grafana-oncall-app",
"version": "1.0.0", "version": "1.9.23",
"description": "Grafana OnCall Plugin", "description": "Grafana OnCall Plugin",
"scripts": { "scripts": {
"lint": "eslint --ext .js,.jsx,.ts,.tsx --max-warnings=20 ./src ./e2e-tests", "lint": "eslint --ext .js,.jsx,.ts,.tsx --max-warnings=20 ./src ./e2e-tests",
@ -11,9 +11,8 @@
"build:dev": "NODE_ENV=development webpack -c ./webpack.config.ts --env development", "build:dev": "NODE_ENV=development webpack -c ./webpack.config.ts --env development",
"labels:link": "pnpm --dir ../../gops-labels/frontend link && pnpm link \"@grafana/labels\" && pnpm --dir ../../gops-labels/frontend watch", "labels:link": "pnpm --dir ../../gops-labels/frontend link && pnpm link \"@grafana/labels\" && pnpm --dir ../../gops-labels/frontend watch",
"labels:unlink": "pnpm --dir ../../gops-labels/frontend unlink", "labels:unlink": "pnpm --dir ../../gops-labels/frontend unlink",
"mage:build-dev": "go mod download && mage -v build:debug", "mage:build-dev": "mage -v build:debug",
"mage:watch": "go mod download && mage -v watch", "mage:watch": "mage -v watch",
"mod:download": "go mod download",
"test-utc": "TZ=UTC jest --verbose --testNamePattern '^((?!@london-tz).)*$'", "test-utc": "TZ=UTC jest --verbose --testNamePattern '^((?!@london-tz).)*$'",
"test-london-tz": "TZ=Europe/London jest --verbose --testNamePattern '@london-tz'", "test-london-tz": "TZ=Europe/London jest --verbose --testNamePattern '@london-tz'",
"test": "PLUGIN_ID=grafana-oncall-app pnpm test-utc && pnpm test-london-tz", "test": "PLUGIN_ID=grafana-oncall-app pnpm test-utc && pnpm test-london-tz",

View file

@ -105,8 +105,8 @@ type OnCallSettingsCache struct {
otherPluginSettingsExpiry time.Time otherPluginSettingsExpiry time.Time
} }
const CLOUD_VERSION_PATTERN = `^(r\d+-v?\d+\.\d+\.\d+|^github-actions-\d+)$` const CLOUD_VERSION_PATTERN = `^(v\d+\.\d+\.\d+|github-actions-[a-zA-Z0-9-]+)$`
const OSS_VERSION_PATTERN = `^(v?\d+\.\d+\.\d+|dev-oss)$` const OSS_VERSION_PATTERN = `^(\d+\.\d+\.\d+)$`
const CLOUD_LICENSE_NAME = "Cloud" const CLOUD_LICENSE_NAME = "Cloud"
const OPEN_SOURCE_LICENSE_NAME = "OpenSource" const OPEN_SOURCE_LICENSE_NAME = "OpenSource"
const INCIDENT_PLUGIN_ID = "grafana-incident-app" const INCIDENT_PLUGIN_ID = "grafana-incident-app"

View file

@ -24,12 +24,12 @@ export const NotificationPoliciesSelect: FC<Props> = ({ disabled = false, import
{ {
value: NotificationPolicyValue.Default, value: NotificationPolicyValue.Default,
label: 'Default', label: 'Default',
description: 'Use "Default notifications" from users personal settings', description: 'Use "Default notification rules" from users personal settings',
}, },
{ {
value: NotificationPolicyValue.Important, value: NotificationPolicyValue.Important,
label: 'Important', label: 'Important',
description: 'Use "Important notifications" from users personal settings', description: 'Use "Important notification rules" from users personal settings',
}, },
]} ]}
onChange={onChange} onChange={onChange}

View file

@ -19,8 +19,6 @@ import { isTopNavbar } from 'plugin/GrafanaPluginRootPage.helpers';
import { AppFeature } from 'state/features'; import { AppFeature } from 'state/features';
import { useStore } from 'state/useStore'; import { useStore } from 'state/useStore';
import plugin from '../../../package.json'; // eslint-disable-line
enum AlertID { enum AlertID {
CONNECTIVITY_WARNING = 'Connectivity Warning', CONNECTIVITY_WARNING = 'Connectivity Warning',
USER_GOOGLE_OAUTH2_TOKEN_MISSING_SCOPES = 'User Google OAuth2 token is missing scopes', USER_GOOGLE_OAUTH2_TOKEN_MISSING_SCOPES = 'User Google OAuth2 token is missing scopes',
@ -59,7 +57,6 @@ export const Alerts = observer(() => {
organizationStore: { currentOrganization }, organizationStore: { currentOrganization },
} = store; } = store;
const versionMismatchLocalStorageId = `version_mismatch_${store.backendVersion}_${plugin?.version}`;
const isChatOpsConnected = getIfChatOpsConnected(currentUser); const isChatOpsConnected = getIfChatOpsConnected(currentUser);
const isPhoneVerified = currentUser?.cloud_connection_status === 3 || currentUser?.verified_phone_number; const isPhoneVerified = currentUser?.cloud_connection_status === 3 || currentUser?.verified_phone_number;
@ -70,7 +67,6 @@ export const Alerts = observer(() => {
!showSlackInstallAlert && !showSlackInstallAlert &&
!showCurrentUserGoogleOAuth2TokenIsMissingScopes() && !showCurrentUserGoogleOAuth2TokenIsMissingScopes() &&
!showBannerTeam() && !showBannerTeam() &&
!showMismatchWarning() &&
!showChannelWarnings() !showChannelWarnings()
) { ) {
return null; return null;
@ -117,30 +113,6 @@ export const Alerts = observer(() => {
/> />
</Alert> </Alert>
)} )}
{showMismatchWarning() && (
<Alert
className={styles.alert}
severity="warning"
title={'Version mismatch!'}
onRemove={getRemoveAlertHandler(versionMismatchLocalStorageId)}
>
Please make sure you have the same versions of the Grafana OnCall plugin and the Grafana OnCall engine,
otherwise there could be issues with your Grafana OnCall installation!
<br />
{`Current plugin version: ${plugin.version}, current engine version: ${store.backendVersion}`}
<br />
Please see{' '}
<a
href={'https://grafana.com/docs/oncall/latest/open-source/#update-grafana-oncall-oss'}
target="_blank"
rel="noreferrer"
className={styles.instructionsLink}
>
the update instructions
</a>
.
</Alert>
)}
{showChannelWarnings() && ( {showChannelWarnings() && (
<Alert <Alert
onRemove={getRemoveAlertHandler(AlertID.CONNECTIVITY_WARNING)} onRemove={getRemoveAlertHandler(AlertID.CONNECTIVITY_WARNING)}
@ -169,16 +141,6 @@ export const Alerts = observer(() => {
return Boolean(currentOrganization?.banner?.title) && !getItem(currentOrganization?.banner?.title); return Boolean(currentOrganization?.banner?.title) && !getItem(currentOrganization?.banner?.title);
} }
function showMismatchWarning(): boolean {
return (
store.isOpenSource &&
store.backendVersion &&
plugin?.version &&
store.backendVersion !== plugin?.version &&
!getItem(versionMismatchLocalStorageId)
);
}
function showChannelWarnings(): boolean { function showChannelWarnings(): boolean {
return Boolean( return Boolean(
currentOrganization && currentOrganization &&

View file

@ -162,7 +162,7 @@ export const IntegrationTemplate = observer((props: IntegrationTemplateProps) =>
return ( return (
<Drawer <Drawer
title={ title={
<div> <div className={styles.titleContainer}>
<Stack justifyContent="space-between" alignItems="flex-start"> <Stack justifyContent="space-between" alignItems="flex-start">
<Stack direction="column"> <Stack direction="column">
<Text.Title level={3}>Edit {template.displayName} template</Text.Title> <Text.Title level={3}>Edit {template.displayName} template</Text.Title>
@ -188,26 +188,28 @@ export const IntegrationTemplate = observer((props: IntegrationTemplateProps) =>
closeOnMaskClick={false} closeOnMaskClick={false}
width={'95%'} width={'95%'}
> >
<div> <div className={styles.containerWrapper}>
<TemplatesAlertGroupsList <div className={styles.container}>
templatePage={TemplatePage.Integrations} <TemplatesAlertGroupsList
alertReceiveChannelId={id} templatePage={TemplatePage.Integrations}
onEditPayload={onEditPayload} alertReceiveChannelId={id}
onSelectAlertGroup={onSelectAlertGroup} onEditPayload={onEditPayload}
templates={templates} onSelectAlertGroup={onSelectAlertGroup}
onLoadAlertGroupsList={onLoadAlertGroupsList} templates={templates}
/> onLoadAlertGroupsList={onLoadAlertGroupsList}
{renderCheatSheet()} />
<TemplateResult {renderCheatSheet()}
alertReceiveChannelId={id} <TemplateResult
template={template} alertReceiveChannelId={id}
templateBody={changedTemplateBody} template={template}
isAlertGroupExisting={isRecentAlertGroupExisting} templateBody={changedTemplateBody}
chatOpsPermalink={chatOpsPermalink} isAlertGroupExisting={isRecentAlertGroupExisting}
payload={alertGroupPayload} chatOpsPermalink={chatOpsPermalink}
error={resultError} payload={alertGroupPayload}
onSaveAndFollowLink={onSaveAndFollowLink} error={resultError}
/> onSaveAndFollowLink={onSaveAndFollowLink}
/>
</div>
</div> </div>
</Drawer> </Drawer>
); );

View file

@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
import { css, cx } from '@emotion/css'; import { css, cx } from '@emotion/css';
import { Button, Icon, LoadingPlaceholder, Stack, useStyles2 } from '@grafana/ui'; import { Button, Icon, LoadingPlaceholder, Stack, useStyles2 } from '@grafana/ui';
import { UserActions } from 'helpers/authorization/authorization'; import { UserActions } from 'helpers/authorization/authorization';
import { StackSize } from 'helpers/consts'; import { IS_CURRENT_ENV_CLOUD, StackSize } from 'helpers/consts';
import { isMobile, openNotification, openWarningNotification, openErrorNotification } from 'helpers/helpers'; import { isMobile, openNotification, openWarningNotification, openErrorNotification } from 'helpers/helpers';
import { useInitializePlugin } from 'helpers/hooks'; import { useInitializePlugin } from 'helpers/hooks';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
@ -140,7 +140,7 @@ export const MobileAppConnection = observer(({ userPk }: Props) => {
// Show link to cloud page for OSS instances with no cloud connection // Show link to cloud page for OSS instances with no cloud connection
if ( if (
store.isOpenSource && !IS_CURRENT_ENV_CLOUD &&
store.hasFeature(AppFeature.CloudConnection) && store.hasFeature(AppFeature.CloudConnection) &&
!cloudStore.cloudConnectionStatus.cloud_connection_status !cloudStore.cloudConnectionStatus.cloud_connection_status
) { ) {
@ -191,7 +191,7 @@ export const MobileAppConnection = observer(({ userPk }: Props) => {
<QRCode className={cx({ [styles.qrCode]: true, [styles.blurry]: isQRBlurry })} value={QRCodeValue} /> <QRCode className={cx({ [styles.qrCode]: true, [styles.blurry]: isQRBlurry })} value={QRCodeValue} />
{isQRBlurry && <QRLoading />} {isQRBlurry && <QRLoading />}
</div> </div>
{store.isOpenSource && QRCodeDataParsed && ( {!IS_CURRENT_ENV_CLOUD && QRCodeDataParsed && (
<Text type="secondary"> <Text type="secondary">
Server URL embedded in this QR: Server URL embedded in this QR:
<br /> <br />

View file

@ -64,7 +64,7 @@ export const PersonalNotificationSettings = observer((props: PersonalNotificatio
const title = ( const title = (
<Text.Title level={5}> <Text.Title level={5}>
<Stack> <Stack>
{isImportant ? 'Important Notifications' : 'Default Notifications'} {isImportant ? 'Important notification rules' : 'Default notification rules'}
<Tooltip <Tooltip
placement="top" placement="top"
content={ content={
@ -154,7 +154,7 @@ export const PersonalNotificationSettings = observer((props: PersonalNotificatio
<div className={styles.step}> <div className={styles.step}>
<WithPermissionControlTooltip userAction={userAction}> <WithPermissionControlTooltip userAction={userAction}>
<Button icon="plus" variant="secondary" fill="text" onClick={getAddNotificationPolicyHandler()}> <Button icon="plus" variant="secondary" fill="text" onClick={getAddNotificationPolicyHandler()}>
Add Notification Step Add notification step
</Button> </Button>
</WithPermissionControlTooltip> </WithPermissionControlTooltip>
</div> </div>

View file

@ -8,11 +8,12 @@ import {
DEFAULT_PAGE, DEFAULT_PAGE,
DOCS_ONCALL_OSS_INSTALL, DOCS_ONCALL_OSS_INSTALL,
DOCS_SERVICE_ACCOUNTS, DOCS_SERVICE_ACCOUNTS,
IS_CURRENT_ENV_CLOUD,
PLUGIN_CONFIG, PLUGIN_CONFIG,
PLUGIN_ROOT, PLUGIN_ROOT,
REQUEST_HELP_URL, REQUEST_HELP_URL,
} from 'helpers/consts'; } from 'helpers/consts';
import { getIsExternalServiceAccountFeatureAvailable, getIsRunningOpenSourceVersion } from 'helpers/helpers'; import { getIsExternalServiceAccountFeatureAvailable } from 'helpers/helpers';
import { useOnMount } from 'helpers/hooks'; import { useOnMount } from 'helpers/hooks';
import { validateURL } from 'helpers/string'; import { validateURL } from 'helpers/string';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
@ -50,7 +51,7 @@ export const PluginConfigPage = observer((props: PluginConfigPageProps<PluginMet
> >
Configure Grafana OnCall Configure Grafana OnCall
</Text.Title> </Text.Title>
{getIsRunningOpenSourceVersion() ? <OSSPluginConfigPage {...props} /> : <CloudPluginConfigPage {...props} />} {IS_CURRENT_ENV_CLOUD ? <CloudPluginConfigPage {...props} /> : <OSSPluginConfigPage {...props} />}
</Stack> </Stack>
); );
}); });

View file

@ -1,8 +1,7 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import { Button, Stack, LoadingPlaceholder } from '@grafana/ui'; import { Button, Stack, LoadingPlaceholder } from '@grafana/ui';
import { REQUEST_HELP_URL, PLUGIN_CONFIG } from 'helpers/consts'; import { REQUEST_HELP_URL, PLUGIN_CONFIG, IS_CURRENT_ENV_CLOUD } from 'helpers/consts';
import { getIsRunningOpenSourceVersion } from 'helpers/helpers';
import { useInitializePlugin } from 'helpers/hooks'; import { useInitializePlugin } from 'helpers/hooks';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { useNavigate } from 'react-router-dom-v5-compat'; import { useNavigate } from 'react-router-dom-v5-compat';
@ -34,12 +33,11 @@ export const PluginInitializer: FC<PluginInitializerProps> = observer(({ childre
}); });
const PluginNotConnectedFullPageError = observer(() => { const PluginNotConnectedFullPageError = observer(() => {
const isOpenSource = getIsRunningOpenSourceVersion();
const isCurrentUserAdmin = window.grafanaBootData.user.orgRole === 'Admin'; const isCurrentUserAdmin = window.grafanaBootData.user.orgRole === 'Admin';
const navigate = useNavigate(); const navigate = useNavigate();
const getSubtitleExtension = () => { const getSubtitleExtension = () => {
if (!isOpenSource) { if (IS_CURRENT_ENV_CLOUD) {
return 'request help from our support team.'; return 'request help from our support team.';
} }
return isCurrentUserAdmin return isCurrentUserAdmin
@ -61,8 +59,8 @@ const PluginNotConnectedFullPageError = observer(() => {
<Button variant="secondary" onClick={() => window.location.reload()}> <Button variant="secondary" onClick={() => window.location.reload()}>
Retry Retry
</Button> </Button>
{!isOpenSource && <Button onClick={() => window.open(REQUEST_HELP_URL, '_blank')}>Request help</Button>} {IS_CURRENT_ENV_CLOUD && <Button onClick={() => window.open(REQUEST_HELP_URL, '_blank')}>Request help</Button>}
{isOpenSource && isCurrentUserAdmin && ( {!IS_CURRENT_ENV_CLOUD && isCurrentUserAdmin && (
<Button onClick={() => navigate(PLUGIN_CONFIG)}>Open configuration</Button> <Button onClick={() => navigate(PLUGIN_CONFIG)}>Open configuration</Button>
)} )}
</Stack> </Stack>

View file

@ -9,13 +9,22 @@ export const PluginId = {
} as const; } as const;
export type PluginId = (typeof PluginId)[keyof typeof PluginId]; export type PluginId = (typeof PluginId)[keyof typeof PluginId];
export const getIsDevelopmentEnv = () => { // Determine current environment: cloud, oss or local
const CLOUD_VERSION_REGEX = /^(v\d+\.\d+\.\d+|github-actions-[a-zA-Z0-9-]+)$/
const determineCurrentEnv = (): 'oss' | 'cloud' | 'local' => {
if (CLOUD_VERSION_REGEX.test(plugin?.version)) {
return 'cloud';
}
try { try {
return process.env.NODE_ENV === 'development'; return process.env.NODE_ENV === 'development' ? 'local' : 'oss';
} catch (error) { } catch (error) {
return false; return 'cloud';
} }
}; };
const CURRENT_ENV = determineCurrentEnv();
export const IS_CURRENT_ENV_CLOUD = CURRENT_ENV === 'cloud';
export const IS_CURRENT_ENV_OSS = CURRENT_ENV === 'oss';
export const IS_CURRENT_ENV_LOCAL = CURRENT_ENV === 'local';
export const getPluginId = (): PluginId => { export const getPluginId = (): PluginId => {
try { try {
@ -28,17 +37,6 @@ export const getPluginId = (): PluginId => {
// Navbar // Navbar
export const APP_SUBTITLE = `Developer-friendly incident response (${plugin?.version})`; export const APP_SUBTITLE = `Developer-friendly incident response (${plugin?.version})`;
export const APP_VERSION = `${plugin?.version}`;
export const CLOUD_VERSION_REGEX = new RegExp('^(r[\\d]+-v[\\d]+.[\\d]+.[\\d]+|github-actions-[\\d]+)$');
// License
export const GRAFANA_LICENSE_OSS = 'OpenSource';
export const GRAFANA_LICENSE_CLOUD = 'Cloud';
export const FALLBACK_LICENSE = CLOUD_VERSION_REGEX.test(APP_VERSION) ? GRAFANA_LICENSE_CLOUD : GRAFANA_LICENSE_OSS;
// height of new Grafana sticky header with breadcrumbs // height of new Grafana sticky header with breadcrumbs
export const GRAFANA_HEADER_HEIGHT = 80; export const GRAFANA_HEADER_HEIGHT = 80;

View file

@ -22,6 +22,12 @@ jest.mock('@grafana/faro-web-tracing', () => ({
TracingInstrumentation: jest.fn(), TracingInstrumentation: jest.fn(),
})); }));
jest.mock('./consts', () => ({
__esModule: true,
...jest.requireActual('./consts'),
IS_CURRENT_ENV_CLOUD: true,
}));
describe('Faro', () => { describe('Faro', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();

View file

@ -6,10 +6,10 @@ import {
FARO_ENDPOINT_DEV, FARO_ENDPOINT_DEV,
FARO_ENDPOINT_OPS, FARO_ENDPOINT_OPS,
FARO_ENDPOINT_PROD, FARO_ENDPOINT_PROD,
IS_CURRENT_ENV_CLOUD,
ONCALL_DEV, ONCALL_DEV,
ONCALL_OPS, ONCALL_OPS,
ONCALL_PROD, ONCALL_PROD,
getIsDevelopmentEnv,
getPluginId, getPluginId,
} from './consts'; } from './consts';
import { safeJSONStringify } from './string'; import { safeJSONStringify } from './string';
@ -33,7 +33,7 @@ class BaseFaroHelper {
faro: Faro; faro: Faro;
initializeFaro(onCallApiUrl: string) { initializeFaro(onCallApiUrl: string) {
if (this.faro || getIsDevelopmentEnv()) { if (this.faro || !IS_CURRENT_ENV_CLOUD) {
return undefined; return undefined;
} }

View file

@ -8,8 +8,6 @@ import { isArray, concat, every, isEmpty, isObject, isPlainObject, flatMap, map,
import { isNetworkError } from 'network/network'; import { isNetworkError } from 'network/network';
import { CLOUD_VERSION_REGEX, getPluginId } from './consts';
export class KeyValuePair<T = string | number> { export class KeyValuePair<T = string | number> {
key: T; key: T;
value: string; value: string;
@ -153,8 +151,6 @@ export const isCurrentGrafanaVersionEqualOrGreaterThan = ({
); );
}; };
export const getIsRunningOpenSourceVersion = () => !CLOUD_VERSION_REGEX.test(config.apps[getPluginId()]?.version);
export const getIsExternalServiceAccountFeatureAvailable = () => export const getIsExternalServiceAccountFeatureAvailable = () =>
isCurrentGrafanaVersionEqualOrGreaterThan({ minMajor: 10, minMinor: 3 }) && isCurrentGrafanaVersionEqualOrGreaterThan({ minMajor: 10, minMinor: 3 }) &&
config.featureToggles.externalServiceAccounts; config.featureToggles.externalServiceAccounts;

View file

@ -2,19 +2,17 @@ import React from 'react';
import { cx } from '@emotion/css'; import { cx } from '@emotion/css';
import { Card, Stack, useStyles2 } from '@grafana/ui'; import { Card, Stack, useStyles2 } from '@grafana/ui';
import { APP_SUBTITLE } from 'helpers/consts'; import { APP_SUBTITLE, IS_CURRENT_ENV_OSS } from 'helpers/consts';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import gitHubStarSVG from 'assets/img/github_star.svg'; import gitHubStarSVG from 'assets/img/github_star.svg';
import logo from 'assets/img/logo.svg'; import logo from 'assets/img/logo.svg';
import { Alerts } from 'containers/Alerts/Alerts'; import { Alerts } from 'containers/Alerts/Alerts';
import { isTopNavbar } from 'plugin/GrafanaPluginRootPage.helpers'; import { isTopNavbar } from 'plugin/GrafanaPluginRootPage.helpers';
import { useStore } from 'state/useStore';
import { getHeaderStyles } from './Header.styles'; import { getHeaderStyles } from './Header.styles';
export const Header = observer(() => { export const Header = observer(() => {
const store = useStore();
const styles = useStyles2(getHeaderStyles); const styles = useStyles2(getHeaderStyles);
return ( return (
@ -34,7 +32,7 @@ export const Header = observer(() => {
); );
function renderHeading() { function renderHeading() {
if (store.isOpenSource) { if (IS_CURRENT_ENV_OSS) {
return ( return (
<div className={cx('heading')}> <div className={cx('heading')}>
<h1 className={styles.pageHeaderTitle}>Grafana OnCall</h1> <h1 className={styles.pageHeaderTitle}>Grafana OnCall</h1>

View file

@ -16,7 +16,7 @@ import {
useSceneApp, useSceneApp,
} from '@grafana/scenes'; } from '@grafana/scenes';
import { Alert, LoadingPlaceholder, Stack, useStyles2 } from '@grafana/ui'; import { Alert, LoadingPlaceholder, Stack, useStyles2 } from '@grafana/ui';
import { DOCS_ROOT, StackSize, PLUGIN_ROOT } from 'helpers/consts'; import { DOCS_ROOT, StackSize, PLUGIN_ROOT, IS_CURRENT_ENV_OSS } from 'helpers/consts';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { Text } from 'components/Text/Text'; import { Text } from 'components/Text/Text';
@ -40,7 +40,6 @@ import getVariables from './variables';
export const Insights = observer(() => { export const Insights = observer(() => {
const { const {
isOpenSource,
insightsDatasource, insightsDatasource,
organizationStore: { currentOrganization }, organizationStore: { currentOrganization },
} = useStore(); } = useStore();
@ -49,11 +48,11 @@ export const Insights = observer(() => {
const config = useMemo( const config = useMemo(
() => ({ () => ({
isOpenSource, isOpenSource: IS_CURRENT_ENV_OSS,
datasource: { uid: isOpenSource ? '$datasource' : insightsDatasource }, datasource: { uid: IS_CURRENT_ENV_OSS ? '$datasource' : insightsDatasource },
stack: currentOrganization?.stack_slug, stack: currentOrganization?.stack_slug,
}), }),
[isOpenSource, currentOrganization?.stack_slug] [currentOrganization?.stack_slug]
); );
const variables = useMemo(() => getVariables(config), [config]); const variables = useMemo(() => getVariables(config), [config]);
@ -69,7 +68,7 @@ export const Insights = observer(() => {
return undefined; return undefined;
} }
const dataSourceListener = const dataSourceListener =
isOpenSource && IS_CURRENT_ENV_OSS &&
variables.datasource.subscribeToState(({ text }) => { variables.datasource.subscribeToState(({ text }) => {
setDatasource(`${text}`); setDatasource(`${text}`);
}); });
@ -86,7 +85,7 @@ export const Insights = observer(() => {
<InsightsGeneralInfo /> <InsightsGeneralInfo />
{isAnyAlertCreatedMoreThan20SecsAgo ? ( {isAnyAlertCreatedMoreThan20SecsAgo ? (
<> <>
{isOpenSource && !datasource && <NoDatasourceWarning />} {IS_CURRENT_ENV_OSS && !datasource && <NoDatasourceWarning />}
<appScene.Component model={appScene} /> <appScene.Component model={appScene} />
</> </>
) : ( ) : (

View file

@ -4,6 +4,7 @@ import { css } from '@emotion/css';
import { AppRootProps, GrafanaTheme2 } from '@grafana/data'; import { AppRootProps, GrafanaTheme2 } from '@grafana/data';
import { Alert, Icon, Stack, Themeable2, withTheme2 } from '@grafana/ui'; import { Alert, Icon, Stack, Themeable2, withTheme2 } from '@grafana/ui';
import { LocationHelper } from 'helpers/LocationHelper'; import { LocationHelper } from 'helpers/LocationHelper';
import { IS_CURRENT_ENV_OSS } from 'helpers/consts';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { VerticalTabsBar, VerticalTab } from 'components/VerticalTabsBar/VerticalTabsBar'; import { VerticalTabsBar, VerticalTab } from 'components/VerticalTabsBar/VerticalTabsBar';
@ -46,10 +47,10 @@ export class _ChatOpsPage extends React.Component<ChatOpsProps, ChatOpsState> {
render() { render() {
const { activeTab } = this.state; const { activeTab } = this.state;
const { store, theme } = this.props; const { theme } = this.props;
const styles = getStyles(theme); const styles = getStyles(theme);
if (!this.isChatOpsConfigured() && store.isOpenSource) { if (!this.isChatOpsConfigured() && IS_CURRENT_ENV_OSS) {
return this.renderNoChatOpsBannerInfo(); return this.renderNoChatOpsBannerInfo();
} }

View file

@ -352,11 +352,11 @@ class Users extends React.Component<UsersProps, UsersState> {
// Show warnining if no notifications are set // Show warnining if no notifications are set
if (!this.renderNotificationsChain(user)) { if (!this.renderNotificationsChain(user)) {
warnings.push('No Default Notifications'); warnings.push('No default notification rules');
} }
if (!this.renderImportantNotificationsChain(user)) { if (!this.renderImportantNotificationsChain(user)) {
warnings.push('No Important Notifications'); warnings.push('No important notification rules');
} }
let phone_verified = user.verified_phone_number !== null; let phone_verified = user.verified_phone_number !== null;
@ -428,13 +428,13 @@ class Users extends React.Component<UsersProps, UsersState> {
}, },
{ {
width: '20%', width: '20%',
title: 'Default Notifications', title: 'Default notification rules',
key: 'notifications-chain', key: 'notifications-chain',
render: this.renderNotificationsChain, render: this.renderNotificationsChain,
}, },
{ {
width: '20%', width: '20%',
title: 'Important Notifications', title: 'Important notification rules',
key: 'important-notifications-chain', key: 'important-notifications-chain',
render: this.renderImportantNotificationsChain, render: this.renderImportantNotificationsChain,
}, },

View file

@ -1,8 +1,7 @@
import { OnCallAppPluginMeta } from 'app-types'; import { OnCallAppPluginMeta } from 'app-types';
import { retryFailingPromises } from 'helpers/async'; import { retryFailingPromises } from 'helpers/async';
import { APP_VERSION, CLOUD_VERSION_REGEX, GRAFANA_LICENSE_CLOUD, GRAFANA_LICENSE_OSS } from 'helpers/consts';
import { loadJs } from 'helpers/loadJs'; import { loadJs } from 'helpers/loadJs';
import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { action, makeObservable, observable, runInAction } from 'mobx';
import qs from 'query-string'; import qs from 'query-string';
import { AlertReceiveChannelStore } from 'models/alert_receive_channel/alert_receive_channel'; import { AlertReceiveChannelStore } from 'models/alert_receive_channel/alert_receive_channel';
@ -43,12 +42,6 @@ export class RootBaseStore {
@observable @observable
isBasicDataLoaded = false; isBasicDataLoaded = false;
@observable
backendVersion = '';
@observable
backendLicense = '';
@observable @observable
recaptchaSiteKey = ''; recaptchaSiteKey = '';
@ -148,21 +141,6 @@ export class RootBaseStore {
// todo use AppFeature only // todo use AppFeature only
hasFeature = (feature: string | AppFeature) => this.features?.[feature]; hasFeature = (feature: string | AppFeature) => this.features?.[feature];
get license() {
if (this.backendLicense) {
return this.backendLicense;
}
if (CLOUD_VERSION_REGEX.test(APP_VERSION)) {
return GRAFANA_LICENSE_CLOUD;
}
return GRAFANA_LICENSE_OSS;
}
@computed
get isOpenSource(): boolean {
return this.license === GRAFANA_LICENSE_OSS;
}
@action.bound @action.bound
async updateFeatures() { async updateFeatures() {
const response = await makeRequest('/features/', {}); const response = await makeRequest('/features/', {});

View file

@ -0,0 +1,5 @@
// NOTE: This file is replaced by the release script, so do not expect it to be as it is always.
export const GIT_COMMIT = 'dev';
// Declare a constant that will be updated by release-please action
export const CURRENT_VERSION = '1.9.23' as string; // x-release-please-version