Skip to content

Commit

Permalink
Datanode migration telemetry (#20225)
Browse files Browse the repository at this point in the history
* DATANODE_MIGRATION telemetry progress

* changelog

* Finalized main datanode migration events

* added telemetry events - progress

* fix linter

* SelectMigrationType telemetry

* finalize telemetry events

* fix build

* fix review comments

---------

Co-authored-by: Ousmane SAMBA <[email protected]>
  • Loading branch information
gally47 and ousmaneo authored Sep 6, 2024
1 parent 9d55614 commit d6ffc36
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 47 deletions.
5 changes: 5 additions & 0 deletions changelog/unreleased/issue-20086.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type="a"
message="Datanode migration telemetry"

issues=["20086"]
pulls=["20225"]
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,53 @@ import { Tabs, Tab, Alert } from 'components/bootstrap';
import CACreateForm from 'components/datanode/DataNodeConfiguration/CACreateForm';
import CAUpload from 'components/datanode/DataNodeConfiguration/CAUpload';
import DocumentationLink from 'components/support/DocumentationLink';
import useSendTelemetry from 'logic/telemetry/useSendTelemetry';
import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants';

const StyledAlert = styled(Alert)`
margin-top: 10px;
margin-bottom: 10px;
`;

const TAB_KEYS = ['create', 'upload'];
const CAConfiguration = () => (
<>
<h2>Configure Certificate Authority</h2>
<p>
In this step you can either upload or create a new certificate authority.<br />
The certificate authority will provision and manage certificates for your Data Nodes more easily.
</p>
<StyledAlert bsStyle="info" title="Reusing certificates">
If your existing cluster uses certificates, by default these will get replaced with the Graylog CA
and automatically generated certificates during provisioning of the data nodes in the next step.
If you want to include your own CA, you can upload an existing certificate.
Please see <DocumentationLink page="graylog-data-node" text="Graylog Data Node - Getting Started" /> for more information.
</StyledAlert>
<Tabs defaultActiveKey={TAB_KEYS[0]} id="ca-configurations">
<Tab eventKey={TAB_KEYS[0]} title="Create new CA">
<CACreateForm />
</Tab>
<Tab eventKey={TAB_KEYS[1]} title="Upload CA">
<CAUpload />
</Tab>
</Tabs>
</>
);

const UploadCA = 'Upload CA';

const CAConfiguration = () => {
const sendTelemetry = useSendTelemetry();

const handleTabSwitch = (e) => {
sendTelemetry((e?.target?.innerText === UploadCA)
? TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.CA_UPLOAD_TAB_CLICKED
: TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.CA_CREATE_TAB_CLICKED, {
app_pathname: 'datanode',
app_section: 'migration',
});
};

return (
<>
<h2>Configure Certificate Authority</h2>
<p>
In this step you can either upload or create a new certificate authority.<br />
The certificate authority will provision and manage certificates for your Data Nodes more easily.
</p>
<StyledAlert bsStyle="info" title="Reusing certificates">
If your existing cluster uses certificates, by default these will get replaced with the Graylog CA
and automatically generated certificates during provisioning of the data nodes in the next step.
If you want to include your own CA, you can upload an existing certificate.
Please see <DocumentationLink page="graylog-data-node" text="Graylog Data Node - Getting Started" /> for more information.
</StyledAlert>
<Tabs defaultActiveKey={TAB_KEYS[0]} id="ca-configurations" onClick={handleTabSwitch}>
<Tab eventKey={TAB_KEYS[0]} title="Create new CA">
<CACreateForm />
</Tab>
<Tab eventKey={TAB_KEYS[1]} title={UploadCA}>
<CAUpload />
</Tab>
</Tabs>
</>
);
};

export default CAConfiguration;
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { FormikInput } from 'components/common';
import { Button } from 'components/bootstrap';
import { QUERY_KEY as DATA_NODES_CA_QUERY_KEY } from 'components/datanode/hooks/useDataNodesCA';
import { MIGRATION_STATE_QUERY_KEY } from 'components/datanode/hooks/useMigrationState';
import useSendTelemetry from 'logic/telemetry/useSendTelemetry';
import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants';

type FormValues = {
organization: string
Expand All @@ -39,6 +41,7 @@ const createCA = (caData: FormValues) => fetch(

const CaCreateForm = () => {
const queryClient = useQueryClient();
const sendTelemetry = useSendTelemetry();

const { mutateAsync: onCreateCA } = useMutation(createCA, {
onSuccess: () => {
Expand All @@ -50,7 +53,15 @@ const CaCreateForm = () => {
UserNotification.error(`CA creation failed with error: ${error}`);
},
});
const onSubmit = (formValues: FormValues) => onCreateCA(formValues).catch(() => {});

const onSubmit = (formValues: FormValues) => {
sendTelemetry(TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.CA_CREATE_CA_CLICKED, {
app_pathname: 'datanode',
app_section: 'migration',
});

return onCreateCA(formValues).catch(() => {});
};

return (
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import { qualifyUrl } from 'util/URLUtils';
import { QUERY_KEY as DATA_NODES_CA_QUERY_KEY } from 'components/datanode/hooks/useDataNodesCA';
import UnsecureConnectionAlert from 'preflight/components/ConfigurationWizard/UnsecureConnectionAlert';
import { MIGRATION_STATE_QUERY_KEY } from 'components/datanode/hooks/useMigrationState';
import useSendTelemetry from 'logic/telemetry/useSendTelemetry';
import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants';

type FormValues = {
files?: Array<File>,
Expand Down Expand Up @@ -95,6 +97,7 @@ const Explanation = styled.p`

const CAUpload = () => {
const queryClient = useQueryClient();
const sendTelemetry = useSendTelemetry();
const onRejectUpload = useCallback(() => {
UserNotification.error('CA upload failed');
}, []);
Expand All @@ -110,7 +113,14 @@ const CAUpload = () => {
},
});

const onSubmit = useCallback((formValues: FormValues) => onProcessUpload(formValues).catch(() => {}), [onProcessUpload]);
const onSubmit = useCallback((formValues: FormValues) => {
sendTelemetry(TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.CA_UPLOAD_CA_CLICKED, {
app_pathname: 'datanode',
app_section: 'migration',
});

return onProcessUpload(formValues).catch(() => {});
}, [onProcessUpload, sendTelemetry]);

return (
<Formik<FormValues> initialValues={{}} onSubmit={onSubmit} validate={validate}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ import { ConfigurationType } from 'components/configurations/ConfigurationTypes'
import { IfPermitted, TimeUnitInput, Spinner } from 'components/common';
import useSendTelemetry from 'logic/telemetry/useSendTelemetry';
import Select from 'components/common/Select';
import useLocation from 'routing/useLocation';
import { getPathnameWithoutId } from 'util/URLUtils';
import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants';
import { MIGRATION_STATE_QUERY_KEY } from 'components/datanode/hooks/useMigrationState';

Expand Down Expand Up @@ -121,7 +119,6 @@ const CertificateRenewalPolicyConfig = ({ className }: Props) => {
const { data: currentConfig, isLoading } = useQuery(queryKey, fetchCurrentConfig);

const sendTelemetry = useSendTelemetry();
const { pathname } = useLocation();
const queryClient = useQueryClient();

const { mutateAsync: updateConfig } = useMutation(handleSaveConfig, {
Expand Down Expand Up @@ -166,10 +163,9 @@ const CertificateRenewalPolicyConfig = ({ className }: Props) => {
};

const saveConfig = (values: FormConfig) => {
sendTelemetry(TELEMETRY_EVENT_TYPE.CONFIGURATIONS.CERTIFICATE_RENEWAL_POLICY_UPDATED, {
app_pathname: getPathnameWithoutId(pathname),
app_section: 'certificate-renewal-policy',
app_action_value: 'configuration-save',
sendTelemetry(TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.CR_UPDATE_CONFIGURATION_CLICKED, {
app_pathname: 'datanode',
app_section: 'migration',
});

const newConfig = {
Expand Down Expand Up @@ -202,6 +198,11 @@ const CertificateRenewalPolicyConfig = ({ className }: Props) => {
<Button bsStyle="primary"
bsSize="small"
onClick={() => {
sendTelemetry(TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.CR_EDIT_CONFIGURATION_CLICKED, {
app_pathname: 'datanode',
app_section: 'migration',
});

setShowModal(true);
}}>Edit configuration
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,26 @@ import {
} from 'components/datanode/Constants';
import RemoteReindexingMigration from 'components/datanode/migrations/RemoteReindexingMigration';
import MigrationError from 'components/datanode/migrations/common/MigrationError';
import useSendTelemetry from 'logic/telemetry/useSendTelemetry';
import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants';

const ManualMigrationStep = () => {
const { currentStep } = useMigrationState();
const { onTriggerNextState } = useTriggerMigrationState();
const sendTelemetry = useSendTelemetry();

const onMigrationStepChange = async (step: MigrationActions, args?: StepArgs = {}) => onTriggerNextState({ step, args });

const handleSelectMigrationType = async (step: MigrationActions, args?: StepArgs = {}) => {
sendTelemetry(TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.MIGRATION_TYPE_SELECTED, {
app_pathname: 'datanode',
app_section: 'migration',
event_details: { migration_type: (step === 'SELECT_ROLLING_UPGRADE_MIGRATION') ? 'IN-PLACE' : 'REMOTE REINDEX' },
});

return onTriggerNextState({ step, args });
};

const migrationTypeOptions = [
{ label: 'In-Place migration', value: 'SELECT_ROLLING_UPGRADE_MIGRATION' },
{ label: 'Remote Re-indexing Migration', value: 'SELECT_REMOTE_REINDEX_MIGRATION' },
Expand All @@ -59,7 +72,7 @@ const ManualMigrationStep = () => {
inputId="datanode-migration-type-select"
options={migrationTypeOptions}
matchProp="label"
onChange={onMigrationStepChange}
onChange={handleSelectMigrationType}
value={null} />
</Input>
</form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@
import React from 'react';
import styled, { css } from 'styled-components';

import type { MigrationActions, OnTriggerStepFunction, StepArgs } from 'components/datanode/Types';
import type { MigrationActions, MigrationStateItem, OnTriggerStepFunction, StepArgs } from 'components/datanode/Types';
import { Button, ButtonToolbar } from 'components/bootstrap';
import { MIGRATION_ACTIONS } from 'components/datanode/Constants';
import useMigrationState from 'components/datanode/hooks/useMigrationState';
import useSendTelemetry from 'logic/telemetry/useSendTelemetry';
import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants';
import type { TelemetryEventType } from 'logic/telemetry/TelemetryContext';

const StyledButtonToolbar = styled(ButtonToolbar)(({ theme }) => css`
margin-top: ${theme.spacings.md};
Expand All @@ -45,14 +49,73 @@ type Props = {
children?: React.ReactNode,
}

const getTelemetryEvent = (state: MigrationStateItem, step: MigrationActions): TelemetryEventType => {
switch (state) {
case 'MIGRATION_WELCOME_PAGE':
if (step === 'SHOW_MIGRATION_SELECTION') return TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.WELCOME_GO_TO_MIGRATION_STEPS_CLICKED;
if (step === 'SHOW_RENEWAL_POLICY_CREATION') return TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.WELCOME_CONFIGURE_CERTIFICATE_RENEWAL_POLICY_CLICKED;

return TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.WELCOME_NEXT_CLICKED;
case 'CA_CREATION_PAGE':
if (step === 'SHOW_MIGRATION_SELECTION') return TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.CA_GO_TO_MIGRATION_STEPS_CLICKED;
if (step === 'SHOW_RENEWAL_POLICY_CREATION') return TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.CA_CONFIGURE_CERTIFICATE_RENEWAL_POLICY_CLICKED;

return TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.CA_NEXT_CLICKED;
case 'RENEWAL_POLICY_CREATION_PAGE':
if (step === 'SHOW_MIGRATION_SELECTION') return TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.CR_GO_TO_MIGRATION_STEPS_CLICKED;

return TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.CR_NEXT_CLICKED;
case 'ROLLING_UPGRADE_MIGRATION_WELCOME_PAGE':
return TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.INPLACE_RUN_DIRECTORY_COMPATIBILITY_CHECK_CLICKED;
case 'DIRECTORY_COMPATIBILITY_CHECK_PAGE':
return TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.INPLACE_DIRECTORY_COMPATIBILITY_CHECK_NEXT_CLICKED;
case 'JOURNAL_SIZE_DOWNTIME_WARNING':
return TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.INPLACE_JOURNAL_SIZE_DOWNTIME_WARNING_NEXT_CLICKED;
case 'MESSAGE_PROCESSING_STOP':
return TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.INPLACE_STOP_MESSAGE_PROCESSING_NEXT_CLICKED;
case 'RESTART_GRAYLOG':
return TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.INPLACE_RESTART_GRAYLOG_NEXT_CLICKED;
case 'REMOTE_REINDEX_WELCOME_PAGE':
return TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.REMOTEREINDEX_WELCOME_NEXT_CLICKED;
case 'EXISTING_DATA_MIGRATION_QUESTION_PAGE':
if (step === 'SKIP_EXISTING_DATA_MIGRATION') return TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.REMOTEREINDEX_MIGRATE_EXISTING_DATA_QUESTION_SKIP_CLICKED;

return TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.REMOTEREINDEX_MIGRATE_EXISTING_DATA_QUESTION_NEXT_CLICKED;
case 'MIGRATE_EXISTING_DATA':
if (step === 'START_REMOTE_REINDEX_MIGRATION') return TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.REMOTEREINDEX_MIGRATE_EXISTING_DATA_START_CLICKED;

return TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.REMOTEREINDEX_MIGRATE_EXISTING_DATA_CHECK_CONNECTION_CLICKED;
case 'ASK_TO_SHUTDOWN_OLD_CLUSTER':
return TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.REMOTEREINDEX_SHUTDOWN_OLD_CLUSTER_NEXT_CLICKED;
default:
return null;
}
};

const MigrationStepTriggerButtonToolbar = ({ nextSteps, disabled, onTriggerStep, args, hidden, children }: Props) => {
const sendTelemetry = useSendTelemetry();
const { currentStep } = useMigrationState();

if (hidden) {
return null;
}

const handleButtonClick = (step: MigrationActions) => {
const eventType = getTelemetryEvent(currentStep?.state, step);

if (eventType) {
sendTelemetry(eventType, {
app_pathname: 'datanode',
app_section: 'migration',
});
}

onTriggerStep(step, args);
};

return (
<StyledButtonToolbar>
{getSortedNextSteps(nextSteps).map((step, index) => <Button key={step} bsStyle={index ? 'default' : 'success'} bsSize="small" disabled={disabled} onClick={() => onTriggerStep(step, args)}>{MIGRATION_ACTIONS[step]?.label || 'Next'}</Button>)}
{getSortedNextSteps(nextSteps).map((step, index) => <Button key={step} bsStyle={index ? 'default' : 'success'} bsSize="small" disabled={disabled} onClick={() => handleButtonClick(step)}>{MIGRATION_ACTIONS[step]?.label || 'Next'}</Button>)}
{children}
</StyledButtonToolbar>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ import { qualifyUrl } from 'util/URLUtils';
import UserNotification from 'util/UserNotification';
import { QUERY_KEY as DATA_NODES_CA_QUERY_KEY } from 'preflight/hooks/useDataNodesCA';
import { MIGRATION_STATE_QUERY_KEY } from 'components/datanode/hooks/useMigrationState';
import useSendTelemetry from 'logic/telemetry/useSendTelemetry';
import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants';

const resetMigration = async () => fetch('DELETE', qualifyUrl('/migration/state'));

const ResetMigrationButton = () => {
const queryClient = useQueryClient();
const [showDialog, setShowDialog] = useState(false);
const sendTelemetry = useSendTelemetry();

const { mutateAsync: onResetMigration } = useMutation(resetMigration, {
onSuccess: () => {
Expand All @@ -43,18 +46,34 @@ const ResetMigrationButton = () => {
},
});

const handleResetClick = () => {
sendTelemetry(TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.RESET_MIGRATION_CLICKED, {
app_pathname: 'datanode',
app_section: 'migration',
});

setShowDialog(true);
};

const handleConfirmClick = async () => {
sendTelemetry(TELEMETRY_EVENT_TYPE.DATANODE_MIGRATION.RESET_MIGRATION_CONFIRM_CLICKED, {
app_pathname: 'datanode',
app_section: 'migration',
});

await onResetMigration();
setShowDialog(false);
};

return (
<>
<Button bsStyle="primary" bsSize="small" onClick={() => setShowDialog(true)}>
<Button bsStyle="primary" bsSize="small" onClick={handleResetClick}>
Reset Migration
</Button>
{showDialog && (
<ConfirmDialog title="Reset Migration"
show
onConfirm={async () => {
await onResetMigration();
setShowDialog(false);
}}
onConfirm={handleConfirmClick}
onCancel={() => setShowDialog(false)}>
Are you sure you want to reset the migration?
</ConfirmDialog>
Expand Down
Loading

0 comments on commit d6ffc36

Please sign in to comment.