Skip to content
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

Merged
merged 16 commits into from
May 15, 2018
Merged

Conversation

nreese
Copy link
Contributor

@nreese nreese commented Apr 16, 2018

fixes #16611

Updates tutorial schema by adding statusCheck configuration for instruction sets. Updates UI components to display status check step when statusCheck is present.

screen shot 2018-04-16 at 4 38 35 pm

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

grow={false}
>
<EuiButton
onClick={() => {this.props.onStatusCheck();}}
Copy link
Contributor

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'), {
Copy link
Contributor

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.

Copy link
Contributor Author

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.

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

Copy link
Contributor

@chrisdavies chrisdavies left a 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}
Copy link
Contributor

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') {
Copy link
Contributor

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');
  }
}

Copy link
Contributor

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'}
Copy link
Contributor

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',
Copy link
Contributor

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
Copy link
Contributor

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;
Copy link
Contributor

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;
}
Copy link
Contributor

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}]`, {});

Copy link
Contributor Author

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}
Copy link
Contributor

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) => {
Copy link
Contributor

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.

Copy link
Contributor Author

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) {
Copy link
Contributor

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...

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

Copy link
Contributor

@stacey-gammon stacey-gammon left a 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:

d96bf79

statusMsg = this.renderStatusCheckMsg(msg, 'warning');
}

const checkStausStep = (
Copy link
Contributor

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,
Copy link
Contributor

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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could do hasStatusCheck: !!instructionSet.statusCheck (assuming instructionSet.statusCheck is not a boolean that would just convert it to one). Maybe my local linter is set up differently from the one that our ci uses, but this actually gets called out in my intellij:

screen shot 2018-05-14 at 2 46 13 pm

@@ -77,23 +78,35 @@ export class Tutorial extends React.Component {
}
}

setParamDefaults = () => {
initInstructionsState = () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit: all the () => {..} functions are supposed to end with ;. Guess it's not part of our linter, but it gets called out in my intellij. I think it's part of our style guide.... (could be wrong).

screen shot 2018-05-14 at 2 50 29 pm

let statusCheckState = undefined;
let isCheckingStatus = false;
let hasFailed = false;
if (_.get(this.state, `statusCheck[${index}].hasStatusCheck`, false)) {
Copy link
Contributor

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) => {
Copy link
Contributor

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 };
Copy link
Contributor

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.

@elasticmachine
Copy link
Contributor

💔 Build Failed

Copy link
Contributor

@stacey-gammon stacey-gammon left a 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

@elasticmachine
Copy link
Contributor

💔 Build Failed

@nreese nreese added the Feature:Home Kibana home application label May 15, 2018
@elasticmachine
Copy link
Contributor

💔 Build Failed

@nreese
Copy link
Contributor Author

nreese commented May 15, 2018

x-pack functional test failure

20:32:03    │ proc  [ftr]          └-> Reserved roles are not editable
20:32:03    │ proc  [ftr]            └-> "before each" hook: global before each
20:32:03    │ proc  [ftr]            │ info  Taking screenshot "/var/lib/jenkins/workspace/elastic+kibana+pull-request+multijob-x-pack/kibana/x-pack/test/functional/screenshots/failure/security app Management Security navigation Reserved roles are not editable.png"
20:32:04    │ proc  [ftr]            │ info  Current URL is: http://localhost:5640/app/kibana#/management/security/roles/edit/kibana_dashboard_only_user?_g=()
20:32:04    │ proc  [ftr]            │ info  Saving page source to: /var/lib/jenkins/workspace/elastic+kibana+pull-request+multijob-x-pack/kibana/x-pack/test/functional/failure_debug/html/security app Management Security navigation Reserved roles are not editable.html
20:32:04    │ proc  [ftr]          └- ✖ fail: "security app Management Security navigation Reserved roles are not editable"
20:32:04    │ proc  [ftr]          │          Management
20:32:04    │ proc  [ftr]          │            Security
20:32:04    │ proc  [ftr]          │              navigation
20:32:04    │ proc  [ftr]          │                Reserved roles are not editable:
20:32:04    │ proc  [ftr]          │        Error: expected false to equal true
20:32:04    │ proc  [ftr]          │         at Assertion.assert (node_modules/expect.js/index.js:96:13)
20:32:04    │ proc  [ftr]          │         at Assertion.be.Assertion.equal (node_modules/expect.js/index.js:216:10)
20:32:04    │ proc  [ftr]          │         at Assertion.(anonymous function) [as be] (node_modules/expect.js/index.js:69:24)
20:32:04    │ proc  [ftr]          │         at Context.it (test/functional/apps/security/management.js:142:60)
20:32:04    │ proc  [ftr]          │         at <anonymous>
20:32:04    │ proc  [ftr]          │         at process._tickCallback (internal/process/next_tick.js:188:7)

jenkins, test this

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@nreese nreese merged commit 6811044 into elastic:master May 15, 2018
nreese added a commit to nreese/kibana that referenced this pull request May 15, 2018
* 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
@sorenlouv
Copy link
Member

Thanks for doing this @nreese !

nreese added a commit that referenced this pull request May 16, 2018
* 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
@alexfrancoeur
Copy link

Nice @nreese! Can this be used for beats / logstash tutorials as well? I imagine we could query for an index that is created from the module itself.

cc: @tsg

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Home] Add custom state checks
6 participants