-
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
[ML] Add optional ability to delete target index and index pattern when deleting DFA job #66934
Conversation
Pinging @elastic/ml-ui (:ml) |
.../data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx
Outdated
Show resolved
Hide resolved
.../data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx
Outdated
Show resolved
Hide resolved
|
||
// Check if an user has permission to delete the index & index pattern | ||
useEffect(() => { | ||
const toastNotifications = getToastNotifications(); |
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.
Please access toast notifications from the context instead of importing from the cache
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.
Done in d14fafa
.../data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx
Outdated
Show resolved
Hide resolved
{i18n.translate('xpack.ml.dataframe.analyticsList.deleteModalBody', { | ||
defaultMessage: `Are you sure you want to delete this analytics job? The analytics job's destination index and optional Kibana index pattern will not be deleted.`, | ||
defaultMessage: `Are you sure you want to delete this analytics job?`, | ||
})} | ||
</p> |
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 see you just updated the string, but would be better to use FormattedMessage
component instead of i18n
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.
Done in d14fafa
if ( | ||
err.message === 'no handler found for uri [/_security/user/_has_privileges] and method [POST]' | ||
) { | ||
return err; |
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.
Your function should return a Promise<boolean>
, so returning an error seems wrong 🤔
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.
Fixed in b867ef2
return privilege.index[destinationIndex].delete_index === true; | ||
} catch (err) { | ||
if ( | ||
err.message === 'no handler found for uri [/_security/user/_has_privileges] and method [POST]' |
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 error message could change at any time. you might want to check for status code instead if it's possible
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.
Looking at verifyHasPrivileges
from x-pack/plugins/monitoring/server/lib/elasticsearch/verify_monitoring_auth.js
I think there's a particular reason why it's checked it like that. Let me follow up with the authors of that API.
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 we move to a check similar to our other privilege checks, this error message comparison won't be needed.
I believe the reason this text is checked it because ignoreUnavailable
is set, so the error returned could be a 404 rather than just a privilege error.
Our other privilege checks first confirm that security is enabled before attempting a privilege check so this error message comparison isn't needed.
See my comment further down in this PR regarding our
api/ml/ml_node_count
endpoint.
x-pack/plugins/ml/server/routes/schemas/data_analytics_schema.ts
Outdated
Show resolved
Hide resolved
const indexPatternId = await getIndexPatternId(context, destinationIndex); | ||
|
||
if (indexPatternId) { | ||
const result = deleteIndexPatternById(context, indexPatternId); |
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.
You don't anything with result
, so a variable definition is redundant. You should get an error from eslint
about issues like this one 🤔 . Worth to set up a eslint
plugin for your IDE of choice
...cation/data_frame_analytics/pages/analytics_management/components/analytics_list/_index.scss
Outdated
Show resolved
Hide resolved
import { IndexPatternAttributes } from 'src/plugins/data/server'; | ||
// import { findObjectByTitle } from 'src/plugins/saved_objects/'; | ||
import { mlLog } from '../../client/log'; | ||
export const SAVED_OBJECT_TYPES = { |
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.
We also define these in ml/server/models/data_recognizer/data_recognizer.ts
. Probably worth moving them out to the common
folder - in ml/common/types/kibana.ts
perhaps? But is it being used here?
}; | ||
|
||
export class IndexPatternHandler { | ||
modulesDir = `${__dirname}/modules`; |
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.
Is this needed?
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.
Removed in d14fafa
indexPatternId: string | undefined = undefined; | ||
|
||
constructor( | ||
private callAsCurrentUser: APICaller, |
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 don't think you are using callAsCurrentUser
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.
label={i18n.translate( | ||
'xpack.ml.dataframe.analyticsList.deleteTargetIndexTitle', | ||
{ | ||
defaultMessage: 'Delete index pattern', |
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 it is worth clarifying which index pattern is deleted here, using a message of Delete destination index pattern {indexName}
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.
Done in e5e618f
{userCanDeleteIndex && indexPatternExists && ( | ||
<EuiSwitch | ||
label={i18n.translate( | ||
'xpack.ml.dataframe.analyticsList.deleteTargetIndexTitle', |
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 looks like it is using the same i18n ID as the message above xpack.ml.dataframe.analyticsList.deleteTargetIndexTitle
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.
Fixed in e5e618f
label={i18n.translate( | ||
'xpack.ml.dataframe.analyticsList.deleteTargetIndexTitle', | ||
{ | ||
defaultMessage: 'Delete destination 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 think it is worth clarifying the name of the index name that is being deleted here Delete destination index {indexName}
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.
Done in e5e618f
validate: { | ||
params: analyticsIdSchema, | ||
query: analyticsIdIndexSchema, |
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 rather than passing the name of the index in the query, it should instead pass a boolean deleteDestinationIndex
, which if true
then goes and deletes the index. As it is, someone could in theory pass a completely different index to the endpoint, which seems a bit dangerous. It might make sense to pass two parameters - deleteDestinationIndex
and deleteDestinationIndexPattern
as the modal does allow zero, one or both to be deleted. It would mean one extra query on the server side, to get the destination index for the job, but this approach would make more sense to me.
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.
You're right! This could be a security issue. I'll refactor it to use the boolean instead.
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.
Done in d14fafa
): Promise<boolean> { | ||
let privilege; | ||
try { | ||
privilege = await context.ml!.mlClient.callAsCurrentUser('transport.request', { |
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 can be callAsCurrentUser('ml.privilegeCheck', { body: ... });
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.
Switched call to ml.privilgeCheck
in d14fafa
if (destinationIndex) { | ||
// Verify if user has privilege to delete the destination index | ||
|
||
const userCanDeleteDestIndex = userCanDeleteIndex(context, destinationIndex); |
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.
/api/ml/ml_node_count
might be a good example of how this endpoint can be written.
If the delete index flag is true, you'll need to look up the destination index and if they don't have permission to delete, we should return a response.forbidden();
immediately.
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.
@elasticmachine merge upstream |
[ML] Clarify delete index & ip with names in modal
@@ -60,6 +60,14 @@ export const analyticsIdSchema = schema.object({ | |||
analyticsId: schema.string(), | |||
}); | |||
|
|||
export const analyticsIdIndexSchema = schema.object({ |
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 this should be renamed to something like deleteDataFrameAnalyticsJobSchema
as it is specific to deleting the job.
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.
Done in ba82079
}); | ||
return privilege.index[destinationIndex].delete_index === true; | ||
} catch (err) { | ||
throw err; |
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 try/catch is redundant as throwing err
is the same as not having the catch
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.
Removed in b867ef2
|
||
return ip !== undefined ? ip.id : undefined; | ||
} catch (error) { | ||
throw 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.
these try/catches are redundant as just throwing error
is the same as not having the try/catch
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.
Removed in ba82079
obj => obj.attributes.title.toLowerCase() === indexName.toLowerCase() | ||
); | ||
|
||
return ip !== undefined ? ip.id : undefined; |
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, could be return ip?.id
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.
Updated in ba82079
deleteTargetIndexAcknowledged = true; | ||
} catch (deleteIndexError) { | ||
errorsEncountered.push({ | ||
msg: i18n.translate( |
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.
we don't translate error messages from endpoints
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.
Removed in ba82079
…csJobSchema, and clean up
@elasticmachine merge upstream |
], | ||
}, | ||
}); | ||
return privilege.index[destinationIndex].delete_index === true; |
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.
Will privilege.index[destinationIndex]
always be defined? Might be worth ensuring privilege
and privilege.index[destinationIndex]
are defined to avoid a possible TypeError if one of these is undefined.
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.
privilege.has_all_requested
can be used here too
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.
Done in 5bd97e9
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 change has also introduced a check for privilege.securityDisabled === true
which doesn't exist on the server side.
Looks good overall 👍 Just left a few minor comments. |
@elasticmachine merge upstream |
merge conflict between base and head |
x-pack/test/functional/services/machine_learning/test_resources.ts
Outdated
Show resolved
Hide resolved
if (!privilege) { | ||
return false; | ||
} | ||
return privilege.securityDisabled === true || privilege.has_all_requested === true; |
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.
securityDisabled
does not exist in privilege
, it is only part of our client side ml.hasPrivileges
function.
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.
API integration tests LGTM
@@ -46,4 +67,22 @@ describe('DeleteAction', () => { | |||
|
|||
expect(getByTestId('mlAnalyticsJobDeleteButton')).toHaveAttribute('disabled'); | |||
}); | |||
|
|||
test('When delete modal is open, modal lets user delete target index by default.', () => { |
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.
Usually, test
suite starts with should, e.g. should allow to delete target index by default
.
If you have a common arrangement for tests you should combine them in group, for example
describe('When delete model is open', () => {
beforeEach(async () => {
// your arrangment here to prepare desired condition
})
test('should allow to delete target index by default', async () => {
})
})
success: boolean; | ||
error?: CustomHttpResponseOptions<ResponseError>; | ||
} | ||
interface DeleteDataFrameAnalyticsWithIndexResponse { |
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 guess it'd be better to define somewhere in x-pack/plugins/ml/common/types
because we need this interface both server and client-side
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
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.
Tested after latest edits and LGTM
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.
Latest edits LGTM ⚡
@elasticmachine merge upstream |
💚 Build SucceededHistory
To update your PR or re-run it, just comment with: |
… DFA job (elastic#66934) Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Summary
This PR adds ability to optionally delete 1) the target index and 2) the index pattern associated with the DFA jobs
Client
Example 1: Index pattern does exist
Example 2: Index pattern does not exist for this job
Server
DELETE `${basePath()}/data_frame/analytics/${analyticsId}
route to check for an optional query object which includes thedestinationIndex
passed from the UI.Checklist
Delete any items that are not applicable to this PR.