-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add status check to "add data" tutorials #17732
Conversation
💔 Build Failed |
💔 Build Failed |
💔 Build Failed |
💚 Build Succeeded |
grow={false} | ||
> | ||
<EuiButton | ||
onClick={() => {this.props.onStatusCheck();}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: i think the spacing should be onClick={() => { this.props.onStatusCheck(); }}
or actually I think you can just do:
onClick={this.props.onStatusCheck}
const searchBody = JSON.stringify({ query: esHitsCheckConfig.query, size: 1 }); | ||
const body = `${searchHeader}\n${searchBody}\n`; | ||
|
||
const response = await fetch(this.props.addBasePath('/elasticsearch/_msearch'), { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think _msearch is for when there are multiple queries and here there is only ever one, so you should be able to get away with a single _search, if you adjust your query a bit. Not sure if it really makes any difference though, one way or the other. 🤷♀️ So take this feedback or leave it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using _search
requires putting the index name in the URL and I could not get it work that way. Not sure if its because how the call gets proxied by the Kibana server.
💚 Build Succeeded |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM w/ possible tweaks. Mostly unimportant stylistic things.
@@ -54,8 +55,10 @@ exports[`isCloudEnabled is false should not render instruction toggle when ON_PR | |||
}, | |||
] | |||
} | |||
isCheckingStatus={false} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if these should be an enum instead of a boolean, e.g.
<InstructionSet
status={status} ... />
Where status is one of failed | checking | succeeded
or whatever? It would allow for adding more scenarios later without continuing to layer in boolean properties.
|
||
renderStatusCheck() { | ||
let statusMsg; | ||
if (this.props.statusCheckState === 'complete') { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd consider moving this into its own method, e.g. something like:
statusMsg() {
if (this.props.statusCheckState === 'complete') {
return this.renderStatusCheckMsg(this.props.statusCheckConfig.success || 'Success', 'success');
}
if (this.props.hasStatusCheckFailed) {
return this.renderStatusCheckMsg(this.props.statusCheckConfig.error || 'No data found', 'warning');
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One other thought, based on the original index, is No data found
a reasonable default or should there be some helpful explanation about it not being set up, possible mitigations, etc? Dunnoes.
onClick={this.props.onStatusCheck} | ||
isLoading={this.props.isCheckingStatus} | ||
> | ||
{this.props.statusCheckConfig.btnLabel ? this.props.statusCheckConfig.btnLabel : 'Check status'} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like to simplify these kinds of statements down to this:
this.props.statusCheckConfig.btnLabel || 'Check status'
</Fragment> | ||
); | ||
return { | ||
title: this.props.statusCheckConfig.title ? this.props.statusCheckConfig.title : 'Status Check', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here:
title: this.props.statusCheckConfig.title || 'Status Check'
Not important, just being my typical nitpicky self. :)
@@ -22,6 +22,7 @@ export class Tutorial extends React.Component { | |||
this.state = { | |||
notFound: false, | |||
paramValues: {}, | |||
statusCheck: [], // array holding instruction set status check state |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment here doesn't really add clarity (to me). I'd remove it, but alternatively, you could make it say something that the field name doesn't already indicate.
@@ -104,6 +117,44 @@ export class Tutorial extends React.Component { | |||
}); | |||
} | |||
|
|||
checkInstructionSetStatus = async (instructionSetIndex) => { | |||
let isComplete = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like a good candidate for a bit of a refactor. Additionally, I think it's considered preferable not to mutate component state within setState
, so something like this, maybe?
checkInstructionSetStatus = async (instructionSetIndex) => {
const instructions = this.getInstructions();
const esHitsCheckConfig = _.get(instructions, `instructionSets[${instructionSetIndex}].statusCheck.esHitsCheck`);
const { hasFailed, isComplete } = await this.fetchStatus(esHitsCheckConfig);
this.setState(({ statusCheck }) => ({
statusCheck: {
...statusCheck,
[instructionSetIndex]: {
...statusCheck[instructionSetIndex],
isComplete,
hasFailed,
isFetchingStatus: false,
},
},
}));
}
async fetchStatus(esHitsCheckConfig) {
if (!esHitsCheckConfig) {
return { hasFailed: false, isComplete: false };
}
const searchHeader = JSON.stringify({ index: esHitsCheckConfig.index });
const searchBody = JSON.stringify({ query: esHitsCheckConfig.query, size: 1 });
return await fetch(this.props.addBasePath('/elasticsearch/_msearch'), {
method: 'post',
body: `${searchHeader}\n${searchBody}\n`,
headers: {
accept: 'application/json',
'content-type': 'application/x-ndjson',
'kbn-xsrf': 'kibana',
},
credentials: 'same-origin'
})
.then(res => res.json())
.then(results => ({ hasFailed: false, isComplete: _.get(results, 'responses.[0].hits.hits.length', 0) > 0 }))
.catch(() => ({ hasFailed: true, isComplete: false }));
}
statusCheckState = this.state.statusCheck[index].isComplete ? 'complete' : 'incomplete'; | ||
isCheckingStatus = this.state.statusCheck[index].isFetchingStatus; | ||
hasFailed = this.state.statusCheck[index].hasFailed; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be condensed into this, I think:
const { statusCheckState, isCheckingStatus, hasFailed } = _.get(this, `state.statusCheck[${index}]`, {});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe it is more clear keeping it the way it is
return ( | ||
<InstructionSet | ||
title={instructionSet.title} | ||
instructionVariants={instructionSet.instructionVariants} | ||
statusCheckConfig={instructionSet.statusCheck} | ||
statusCheckState={statusCheckState} | ||
isCheckingStatus={isCheckingStatus} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you do condense (per previous comment) these would need to be coerced, as they could be undefined:
isCheckingStatus={!!isCheckingStatus}
isCheckingStatus={isCheckingStatus} | ||
hasStatusCheckFailed={hasFailed} | ||
onStatusCheck={() => { | ||
this.setState((prevState) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
checkInstructionSetStatus is already setting state within, so maybe, this should be simplified:
onStatusCheck={() => this.checkInstructionSetStatus(index)}
And checkInstructionSetStatus
can be updated to do this before it does other things:
checkInstructionSetStatus = async (instructionSetIndex) => {
this.setState(({ statusCheck }) => ({
statusCheck: {
...statusCheck,
[index]: {
...statusCheck[index],
isFetchingStatus: true,
},
},
}));
// other stuff...
}
Also, I'm curious if lodash has anything like Ramda's assocPath
, which is an immutable set, and would reduce the previous example to this:
checkInstructionSetStatus = async (instructionSetIndex) => {
this.setState(R.assocPath(['statusCheck', index, 'isFetchingStatus'], true));
}
Not sure. Am toggling between review and training, so... not in a position to Google this ATM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am going to leave this the way it is. setState
is not synchronous, and I want isFetchingStatus to be set before checkInstructionSetStatus is called.
let apmIndexPattern = 'apm*'; | ||
try { | ||
apmIndexPattern = server.config().get('xpack.apm.indexPattern'); | ||
} catch (error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense. I wonder if we could modify server.config().get
to take an optional default value and avoid these try/catches everywhere. I've written these same try/catches elsewhere. Seems like something we can clean up somday, just a note...
💚 Build Succeeded |
💔 Build Failed |
💚 Build Succeeded |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had quite a bit of feedback regarding combining some of the props types that @chrisdavies first brought up. In order to work through my comments (and make sure they made sense), I ended up creating the changes myself, so if you decide to move forward with them, feel free to explore, or merge, them:
statusMsg = this.renderStatusCheckMsg(msg, 'warning'); | ||
} | ||
|
||
const checkStausStep = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
checkStatusStep (sp)
statusCheckState: PropTypes.string, | ||
onStatusCheck: PropTypes.func.isRequired, | ||
isCheckingStatus: PropTypes.bool, | ||
hasStatusCheckFailed: PropTypes.bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'm +1 with @chrisdavies on consolidating statusCheckState
, isCheckingStatus
and hasStatusCheckFailed
into an enum (I think I'd use the same statusCheckState
name) and also using PropTypes.oneOf. I think you'd need: notchecked
, checking
, success
, failed
. Also we may eventually want to distinguish between an error (e.g. the es query was malformed and didn't complete) vs the check completed but no hits were returned, and this sets us up better for that.
I think it'd be a little clearer, rather than trying to figure out the combination of everything. As it is now, someone could pass in statusCheckState of incomplete
but hasStatusCheckFailed of true
and that doesn't really make sense. If we use an enum I think we can prevent some weird combinations.
|
||
const statusCheck = instructions.instructionSets.map((instructionSet) => { | ||
return { | ||
hasStatusCheck: instructionSet.statusCheck ? true : false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -77,23 +78,35 @@ export class Tutorial extends React.Component { | |||
} | |||
} | |||
|
|||
setParamDefaults = () => { | |||
initInstructionsState = () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let statusCheckState = undefined; | ||
let isCheckingStatus = false; | ||
let hasFailed = false; | ||
if (_.get(this.state, `statusCheck[${index}].hasStatusCheck`, false)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There any reason to use the lodash version here? Seems like ever instruction set should have hasStatusCheck
variable.
Actually, why have a hasStatusCheck value on state at all? Can you get away with just checking instructionSet.statusCheck
here?
const esHitsCheckConfig = _.get(instructions, `instructionSets[${instructionSetIndex}].statusCheck.esHitsCheck`); | ||
const { hasFailed, isComplete } = await this.fetchEsHitsStatus(esHitsCheckConfig); | ||
|
||
this.setState((prevState) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to ever call setState if there is no esHitsCheck? Maybe pull out the inner
+ if (!esHitsCheckConfig) {
+ return { hasFailed: false, isComplete: false };
+ }
so we can skip the setState call if it doesn't exist.
}); | ||
|
||
if (response.status > 300) { | ||
return { hasFailed: true, isComplete: false }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also pushes me to think an enum would be easier to follow. I thought this would be a bad state, but I guess you are using incomplete plus failed to mean there was an error, rather than a successful check but with no data? Below it's also a bit confusing that it's broken into two states that will always be mutually exclusive.
💔 Build Failed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm pending passing ci
💔 Build Failed |
💔 Build Failed |
x-pack functional test failure
jenkins, test this |
💚 Build Succeeded |
* add statusCheck configuration to instructionSetSchema * add status check step to instruction set * track status check state * query elasticsearch for hits * display message when status check completes * clean up * use callout to display status check results * Updated status check * update tutorial snapshot * add jest tests for InstructionSet component * pass function as prop instead of wrapping in new function * refactor checkInstructionSetStatus * update snapshots that broke after rebase * Suggested changes (#24) * update jest test for statusCheckState prop enum * update tutorial snapshots
Thanks for doing this @nreese ! |
* add statusCheck configuration to instructionSetSchema * add status check step to instruction set * track status check state * query elasticsearch for hits * display message when status check completes * clean up * use callout to display status check results * Updated status check * update tutorial snapshot * add jest tests for InstructionSet component * pass function as prop instead of wrapping in new function * refactor checkInstructionSetStatus * update snapshots that broke after rebase * Suggested changes (#24) * update jest test for statusCheckState prop enum * update tutorial snapshots
fixes #16611
Updates tutorial schema by adding
statusCheck
configuration for instruction sets. Updates UI components to display status check step whenstatusCheck
is present.