Skip to content

Commit

Permalink
Moved the business logic of the configuration steps in the specific s…
Browse files Browse the repository at this point in the history
…teps

- Added an API key validation to determine if the key is valid instead of fetching the user tenants
- Makefile: No need to check if we have python tests anymore, we do have them
- Removed the splunk.min.js file since it's injected by splunk, removed the compilation of javascript files
- Created constants file for typescript since it was getting all over the place
- Added disabled step for buttons to block the user from submitting before the step is loaded
  • Loading branch information
Marc-Antoine Hinse committed Nov 27, 2024
1 parent 06958c0 commit 13f71a7
Show file tree
Hide file tree
Showing 27 changed files with 395 additions and 332 deletions.
4 changes: 1 addition & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,7 @@ inspect-tags:

.PHONY: test
test: venv-tools
@if test -d "./packages/flare/tests" ; then \
venv-tools/bin/pytest ./packages/flare/tests/**/*.py -vv ; \
fi
venv-tools/bin/pytest ./packages/flare/tests/**/*.py -vv ;

.PHONY: format setup-web
format: venv-tools
Expand Down
4 changes: 3 additions & 1 deletion packages/flare/bin/cron_job_ingest_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,9 @@ def fetch_feed(
start_date = get_start_date(kvstore=kvstore)
logger.info(f"Fetching {tenant_id=}, {next=}, {start_date=}")
for event_next in flare_api.fetch_feed_events(
next=next, start_date=start_date, ingest_metadata_only=ingest_metadata_only
next=next,
start_date=start_date,
ingest_metadata_only=ingest_metadata_only,
):
yield event_next
except Exception as e:
Expand Down
5 changes: 5 additions & 0 deletions packages/flare/bin/flare.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ def _fetch_full_event_from_uid(self, *, uid: str) -> dict:
self.logger.debug(event)
return event

def fetch_api_key_validation(self) -> requests.Response:
return self.flare_client.get(
url="/tokens/test",
)

def fetch_tenants(self) -> requests.Response:
return self.flare_client.get(
url="/firework/v2/me/tenants",
Expand Down
30 changes: 22 additions & 8 deletions packages/flare/bin/flare_external_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,32 @@
from logger import Logger


class FlareValidateApiKey(splunk.rest.BaseRestHandler):
def handle_POST(self) -> None:
payload = self.request["payload"]
params = parse.parse_qs(payload)

if "apiKey" not in params:
raise Exception("API Key is required")

flare_api = FlareAPI(api_key=params["apiKey"][0])
flare_api.fetch_api_key_validation()
self.response.setHeader("Content-Type", "application/json")
self.response.write(json.dumps({}))


class FlareUserTenants(splunk.rest.BaseRestHandler):
def handle_POST(self) -> None:
logger = Logger(class_name=__file__)
payload = self.request["payload"]
params = parse.parse_qs(payload)

if "apiKey" in params:
flare_api = FlareAPI(api_key=params["apiKey"][0])
user_tenants_response = flare_api.fetch_tenants()
tenants_response = user_tenants_response.json()
logger.debug(tenants_response)
self.response.setHeader("Content-Type", "application/json")
self.response.write(json.dumps(tenants_response))
else:
if "apiKey" not in params:
raise Exception("API Key is required")

flare_api = FlareAPI(api_key=params["apiKey"][0])
response = flare_api.fetch_tenants()
response_json = response.json()
logger.debug(f"FlareUserTenants: {response_json}")
self.response.setHeader("Content-Type", "application/json")
self.response.write(json.dumps(response_json))
5 changes: 5 additions & 0 deletions packages/flare/src/main/resources/splunk/default/restmap.conf
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
[script:flare_external_requests_api_key_validation]
match=/fetch_api_key_validation
handler=flare_external_requests.FlareValidateApiKey
python.version = python3

[script:flare_external_requests_user_tenants]
match=/fetch_user_tenants
handler=flare_external_requests.FlareUserTenants
Expand Down
4 changes: 4 additions & 0 deletions packages/flare/src/main/resources/splunk/default/web.conf
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
[expose:flare_external_requests_api_key_validation]
pattern=fetch_api_key_validation
methods=POST

[expose:flare_external_requests_user_tenants]
pattern=fetch_user_tenants
methods=POST
19 changes: 13 additions & 6 deletions packages/flare/tests/bin/test_flare_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ def test_flare_full_data_without_metadata(

events: list[dict] = []
for event, next_token in flare_api.fetch_feed_events(
next=None, start_date=None, ingest_metadata_only=True
next=None,
start_date=None,
ingest_metadata_only=True,
):
assert next_token == expected_return_value["next"]
events.append(event)
Expand Down Expand Up @@ -100,7 +102,9 @@ def test_flare_full_data_with_metadata(

events: list[dict] = []
for event, next_token in flare_api.fetch_feed_events(
next=None, start_date=None, ingest_metadata_only=False
next=None,
start_date=None,
ingest_metadata_only=False,
):
assert next_token == expected_return_value["next"]
events.append(event)
Expand Down Expand Up @@ -138,9 +142,12 @@ def test_flare_full_data_with_metadata_and_exception(
flare_api = FlareAPI(api_key="some_key", tenant_id=111)

with pytest.raises(KeyError, match="metadata"):
for _, _ in flare_api.fetch_feed_events(
next=None, start_date=None, ingest_metadata_only=False
):
pass
next(
flare_api.fetch_feed_events(
next=None,
start_date=None,
ingest_metadata_only=False,
)
)

fetch_event_feed_metadata_mock.assert_called_once()
6 changes: 3 additions & 3 deletions packages/react-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
"license": "UNLICENSED",
"scripts": {
"build": "node build.js build",
"eslint": "eslint src --ext \".js,.tsx,.ts\"",
"eslint:fix": "eslint src --ext \".js,.tsx,.ts\" --fix",
"eslint": "eslint src --ext \".tsx,.ts\"",
"eslint:fix": "eslint src --ext \".tsx,.ts\" --fix",
"lint": "yarn run eslint && yarn run stylelint",
"lint:ci": "yarn run eslint:ci && yarn run stylelint",
"start": "webpack --watch",
"stylelint": "stylelint \"src/**/*.{js,jsx}\" --config stylelint.config.js"
"stylelint": "stylelint \"src/**/*.{ts,tsx}\" --config stylelint.config.js"
},
"exports": {
"./configuration-screen": "./ConfigurationScreen.js",
Expand Down
1 change: 1 addition & 0 deletions packages/react-components/src/ConfigurationScreen.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
width: 800px;
gap: 1.5rem;
align-self: center;
padding-bottom: 2rem;
}

.content-step {
Expand Down
202 changes: 36 additions & 166 deletions packages/react-components/src/ConfigurationScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,156 +1,40 @@
import React, { useEffect, useState, FC } from 'react';
import {
appName,
createFlareIndex,
redirectToHomepage,
fetchApiKey,
fetchAvailableIndexNames,
fetchCurrentIndexName,
fetchIngestMetadataOnly,
fetchTenantId,
fetchUserTenants,
saveConfiguration,
} from './utils/setupConfiguration';
import { ConfigurationSteps, Tenant } from './models/flare';
import './global.css';
import React, { FC, useEffect, useState } from 'react';
import './ConfigurationScreen.css';
import ConfigurationCompletedStep from './components/ConfigurationCompletedStep';
import ConfigurationInitialStep from './components/ConfigurationInitialStep';
import ConfigurationUserPreferencesStep from './components/ConfigurationUserPreferencesStep';
import LoadingBar from './components/LoadingBar';
import { toastManager } from './components/ToastManager';
import DoneIcon from './components/icons/DoneIcon';
import ExternalLinkIcon from './components/icons/ExternalLinkIcon';
import { toastManager } from './components/ToastManager';
import ToolIcon from './components/icons/ToolIcon';
import ConfigurationInitialStep from './components/ConfigurationInitialStep';
import ConfigurationUserPreferencesStep from './components/ConfigurationUserPreferencesStep';
import ConfigurationCompletedStep from './components/ConfigurationCompletedStep';

const TOAST_API_KEY_ERROR = 'api_key_error';
const TOAST_TENANT_SUCCESS = 'tenant_success';
import './global.css';
import { ConfigurationStep } from './models/flare';
import { createFlareIndex, fetchApiKey, redirectToHomepage } from './utils/setupConfiguration';

const ConfigurationScreen: FC<{ theme: string }> = ({ theme }) => {
const [configurationStep, setConfigurationStep] = useState(ConfigurationStep.Initial);
const [apiKey, setApiKey] = useState('');
const [tenantId, setTenantId] = useState(-1);
const [indexName, setIndexName] = useState(appName);
const [errorMessage, setErrorMessage] = useState('');
const [tenants, setUserTenants] = useState<Tenant[]>([]);
const [isIngestingMetadataOnly, setIsIngestingMetadataOnly] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [isCompleted, setIsCompleted] = useState(false);
const [indexNames, setIndexNames] = useState<string[]>([]);

toastManager.setTheme(theme);

function reset(): void {
setApiKey('');
setTenantId(-1);
setUserTenants([]);
setIsLoading(false);
setIsCompleted(false);
}

function getCurrentConfigurationStep(): ConfigurationSteps {
if (tenants.length === 0) {
return ConfigurationSteps.Initial;
}
if (!isCompleted) {
return ConfigurationSteps.UserPreferences;
}

return ConfigurationSteps.Completed;
}

const handleApiKeyChange = (e): void => setApiKey(e.target.value);
const handleTenantIdChange = (e): void => setTenantId(parseInt(e.target.value, 10));
const handleIndexNameChange = (e): void => setIndexName(e.target.value);
const handleIsIngestingMetadataChange = (e): void => {
setIsIngestingMetadataOnly(e.target.checked);
};

const handleBackButton = (): void => {
const currentConfigurationStep = getCurrentConfigurationStep();
if (currentConfigurationStep === ConfigurationSteps.Initial) {
if (configurationStep === ConfigurationStep.Initial) {
redirectToHomepage();
} else if (currentConfigurationStep === ConfigurationSteps.UserPreferences) {
setUserTenants([]);
} else if (currentConfigurationStep === ConfigurationSteps.Completed) {
reset();
} else if (configurationStep === ConfigurationStep.UserPreferences) {
setConfigurationStep(ConfigurationStep.Initial);
} else if (configurationStep === ConfigurationStep.Completed) {
setConfigurationStep(ConfigurationStep.Initial);
}
};

const handleSubmitApiKey = (): void => {
setIsLoading(true);
fetchUserTenants(
apiKey,
(userTenants: Tenant[]) => {
if (tenantId === -1 && userTenants.length > 0) {
setTenantId(userTenants[0].id);
}
setErrorMessage('');
setUserTenants(userTenants);
setIsLoading(false);
},
(error: string) => {
setErrorMessage(error);
setIsLoading(false);
toastManager.show({
id: TOAST_API_KEY_ERROR,
isError: true,
content: 'Something went wrong. Please review your form.',
});
}
);
};

const handleSubmitTenant = (): void => {
setIsLoading(true);
saveConfiguration(apiKey, tenantId, indexName, isIngestingMetadataOnly)
.then(() => {
setIsLoading(false);
setIsCompleted(true);
toastManager.destroy(TOAST_API_KEY_ERROR);
toastManager.show({
id: TOAST_TENANT_SUCCESS,
content: 'Configured Flare Account',
});
})
.catch((e: any) => {
setIsLoading(false);
toastManager.show({
id: TOAST_API_KEY_ERROR,
isError: true,
content: `Something went wrong. ${e.responseText}`,
});
});
};

function getSelectedTenantName(): string {
const filteredTenants = tenants.filter((tenant: Tenant) => tenant.id === tenantId);
if (filteredTenants.length > 0) {
return filteredTenants[0].name;
}

return 'unknown';
}

useEffect(() => {
if (isCompleted) {
return;
}
createFlareIndex().then(() => {
Promise.all([
fetchApiKey(),
fetchTenantId(),
fetchIngestMetadataOnly(),
fetchCurrentIndexName(),
fetchAvailableIndexNames(),
]).then(([key, id, ingestMetadataOnly, index, availableIndexNames]) => {
if (configurationStep === ConfigurationStep.Initial) {
Promise.all([fetchApiKey(), createFlareIndex()]).then(([key]) => {
setApiKey(key);
setTenantId(id);
setIsIngestingMetadataOnly(ingestMetadataOnly);
setIndexName(index);
setIndexNames(availableIndexNames);
});
});
}, [isCompleted]);
}
}, [configurationStep]);

useEffect(() => {
const container = document.getElementById('container') as HTMLDivElement;
Expand All @@ -160,51 +44,37 @@ const ConfigurationScreen: FC<{ theme: string }> = ({ theme }) => {
}
}, [theme]);

const currentConfigurationStep = getCurrentConfigurationStep();

return (
<div id="container" className={theme === 'dark' ? 'dark' : ''}>
<LoadingBar
max={Object.keys(ConfigurationSteps).length / 2}
value={currentConfigurationStep}
/>
<LoadingBar max={Object.keys(ConfigurationStep).length / 2} value={configurationStep} />
<div className="content">
<ToolIcon
remSize={6}
hidden={currentConfigurationStep === ConfigurationSteps.Completed}
/>
<DoneIcon
remSize={6}
hidden={currentConfigurationStep !== ConfigurationSteps.Completed}
/>
<ToolIcon remSize={6} hidden={configurationStep === ConfigurationStep.Completed} />
<DoneIcon remSize={6} hidden={configurationStep !== ConfigurationStep.Completed} />
<div className="content-step">
<h2>Configure your Flare Account</h2>
<ConfigurationInitialStep
show={currentConfigurationStep === ConfigurationSteps.Initial}
show={configurationStep === ConfigurationStep.Initial}
configurationStep={configurationStep}
apiKey={apiKey}
errorMessage={errorMessage}
isLoading={isLoading}
onCancelConfigurationClick={handleBackButton}
onSubmitApiKeyClick={handleSubmitApiKey}
onApiKeyChange={handleApiKeyChange}
onApiKeyValidated={(): void =>
setConfigurationStep(ConfigurationStep.UserPreferences)
}
setApiKey={setApiKey}
/>
<ConfigurationUserPreferencesStep
show={currentConfigurationStep === ConfigurationSteps.UserPreferences}
selectedTenantId={tenantId}
tenants={tenants}
selectedIndexName={indexName}
indexNames={indexNames}
isLoading={isLoading}
isIngestingMetadataOnly={isIngestingMetadataOnly}
show={configurationStep === ConfigurationStep.UserPreferences}
configurationStep={configurationStep}
apiKey={apiKey}
onNavigateBackClick={handleBackButton}
onSubmitUserPreferencesClick={handleSubmitTenant}
onTenantIdChange={handleTenantIdChange}
onIndexNameChange={handleIndexNameChange}
onIngestingMetadataChange={handleIsIngestingMetadataChange}
onUserPreferencesSaved={(): void =>
setConfigurationStep(ConfigurationStep.Completed)
}
/>
<ConfigurationCompletedStep
show={currentConfigurationStep === ConfigurationSteps.Completed}
tenantName={getSelectedTenantName()}
show={configurationStep === ConfigurationStep.Completed}
apiKey={apiKey}
configurationStep={configurationStep}
onEditConfigurationClick={handleBackButton}
/>
</div>
Expand Down
Loading

0 comments on commit 13f71a7

Please sign in to comment.