[MM-63555] mmctl: Add compliance export show and cancel cmds (#30569)

* add compliance export cancel command and tests

- Introduced `ComplianceExportCancelCmd` to allow cancellation of compliance export jobs.
- Implemented unit tests for the cancellation command, covering successful cancellation, error handling for non-existent jobs, and cancellation in non-cancellable states.
- Added end-to-end tests to validate the command's functionality in the E2E test suite.

* mmctl docs

* clean up example text; remove unneeded getJob

* fix tests

* fix tests, again.

* prefer hyphen for command naming

* update docs
This commit is contained in:
Christopher Poile 2025-06-24 12:26:43 -04:00 committed by GitHub
parent 9399398e04
commit e60f878090
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 256 additions and 17 deletions

View file

@ -12,7 +12,7 @@ import (
)
var ComplianceExportCmd = &cobra.Command{
Use: "compliance_export",
Use: "compliance-export",
Short: "Management of compliance exports",
}
@ -26,12 +26,20 @@ var ComplianceExportListCmd = &cobra.Command{
var ComplianceExportShowCmd = &cobra.Command{
Use: "show [complianceExportJobID]",
Example: " compliance_export show o98rj3ur83dp5dppfyk5yk6osy",
Example: "compliance-export show o98rj3ur83dp5dppfyk5yk6osy",
Short: "Show compliance export job",
Args: cobra.ExactArgs(1),
RunE: withClient(complianceExportShowCmdF),
}
var ComplianceExportCancelCmd = &cobra.Command{
Use: "cancel [complianceExportJobID]",
Example: "compliance-export cancel o98rj3ur83dp5dppfyk5yk6osy",
Short: "Cancel compliance export job",
Args: cobra.ExactArgs(1),
RunE: withClient(complianceExportCancelCmdF),
}
func init() {
ComplianceExportListCmd.Flags().Int("page", 0, "Page number to fetch for the list of compliance export jobs")
ComplianceExportListCmd.Flags().Int("per-page", DefaultPageSize, "Number of compliance export jobs to be fetched")
@ -40,6 +48,7 @@ func init() {
ComplianceExportCmd.AddCommand(
ComplianceExportListCmd,
ComplianceExportShowCmd,
ComplianceExportCancelCmd,
)
RootCmd.AddCommand(ComplianceExportCmd)
}
@ -58,3 +67,11 @@ func complianceExportShowCmdF(c client.Client, command *cobra.Command, args []st
return nil
}
func complianceExportCancelCmdF(c client.Client, command *cobra.Command, args []string) error {
if _, err := c.CancelJob(context.TODO(), args[0]); err != nil {
return fmt.Errorf("failed to cancel compliance export job: %w", err)
}
return nil
}

View file

@ -198,3 +198,106 @@ func (s *MmctlE2ETestSuite) TestComplianceExportShowCmdE2E() {
s.Require().Equal(job.Id, printer.GetLines()[0].(*model.Job).Id)
})
}
func (s *MmctlE2ETestSuite) TestComplianceExportCancelCmdE2E() {
s.SetupMessageExportTestHelper()
s.Run("no permissions", func() {
printer.Clean()
now := model.GetMillis()
// Create a job
job, _, err := s.th.SystemAdminClient.CreateJob(context.Background(), &model.Job{
Id: st.NewTestID(),
CreateAt: now - 1000,
Status: model.JobStatusInProgress,
Type: model.JobTypeMessageExport,
StartAt: now - 1000,
LastActivityAt: now - 1000,
})
s.Require().NoError(err)
defer func() {
// Ensure job is deleted from the database
var result string
result, err = s.th.App.Srv().Store().Job().Delete(job.Id)
s.Require().NoError(err, "Failed to delete job (result: %v)", result)
}()
cmd := makeCmd()
err = complianceExportCancelCmdF(s.th.Client, cmd, []string{job.Id})
s.Require().EqualError(err, "failed to get compliance export job: You do not have the appropriate permissions.")
s.Require().Empty(printer.GetLines())
s.Require().Empty(printer.GetErrorLines())
})
s.RunForSystemAdminAndLocal("Cancel non-existent job", func(c client.Client) {
printer.Clean()
cmd := makeCmd()
err := complianceExportCancelCmdF(c, cmd, []string{"non-existent-job-id"})
s.Require().EqualError(err, "failed to get compliance export job: Sorry, we could not find the page., There doesn't appear to be an api call for the url='/api/v4/jobs/non-existent-job-id'. Typo? are you missing a team_id or user_id as part of the url?")
s.Require().Empty(printer.GetLines())
s.Require().Empty(printer.GetErrorLines())
})
s.RunForSystemAdminAndLocal("Cancel existing job", func(c client.Client) {
now := model.GetMillis()
// Create a job
job, _, err := s.th.SystemAdminClient.CreateJob(context.Background(), &model.Job{
Id: st.NewTestID(),
CreateAt: now - 1000,
Status: model.JobStatusInProgress,
Type: model.JobTypeMessageExport,
StartAt: now - 1000,
LastActivityAt: now - 1000,
})
s.Require().NoError(err)
defer func() {
// Ensure job is deleted from the database
var result string
result, err = s.th.App.Srv().Store().Job().Delete(job.Id)
s.Require().NoError(err, "Failed to delete job (result: %v)", result)
}()
printer.Clean()
cmd := makeCmd()
err = complianceExportCancelCmdF(c, cmd, []string{job.Id})
s.Require().NoError(err)
s.Require().Empty(printer.GetLines())
s.Require().Empty(printer.GetErrorLines())
// Verify job was cancelled
job, _, err = s.th.SystemAdminClient.GetJob(context.Background(), job.Id)
s.Require().NoError(err)
s.Require().Equal(model.JobStatusCanceled, job.Status)
})
s.RunForSystemAdminAndLocal("Error cancelling job in non-cancellable state", func(c client.Client) {
now := model.GetMillis()
// Create a job
job, _, err := s.th.SystemAdminClient.CreateJob(context.Background(), &model.Job{
Id: st.NewTestID(),
CreateAt: now - 1000,
Status: model.JobStatusInProgress,
Type: model.JobTypeMessageExport,
StartAt: now - 1000,
LastActivityAt: now - 1000,
})
s.Require().NoError(err)
_, err = s.th.SystemAdminClient.UpdateJobStatus(context.Background(), job.Id, model.JobStatusCanceled, true)
s.Require().NoError(err)
defer func() {
// Ensure job is deleted from the database
var result string
result, err = s.th.App.Srv().Store().Job().Delete(job.Id)
s.Require().NoError(err, "Failed to delete job (result: %v)", result)
}()
printer.Clean()
cmd := makeCmd()
err = complianceExportCancelCmdF(c, cmd, []string{job.Id})
s.Require().EqualError(err, "failed to cancel compliance export job: Could not request cancellation for job that is not in a cancelable state.")
s.Require().Empty(printer.GetLines())
s.Require().Empty(printer.GetErrorLines())
})
}

View file

@ -13,6 +13,7 @@ import (
func (s *MmctlUnitTestSuite) TestComplianceExportListCmdF() {
s.Run("list default pagination", func() {
s.SetupTest() // Reset mocks before test
printer.Clean()
var mockJobs []*model.Job
@ -115,6 +116,7 @@ func (s *MmctlUnitTestSuite) TestComplianceExportListCmdF() {
func (s *MmctlUnitTestSuite) TestComplianceExportShowCmdF() {
s.Run("show job successfully", func() {
s.SetupTest() // Reset mocks before test
printer.Clean()
mockJob := &model.Job{
Id: model.NewId(),
@ -137,6 +139,7 @@ func (s *MmctlUnitTestSuite) TestComplianceExportShowCmdF() {
})
s.Run("show job with error", func() {
s.SetupTest() // Reset mocks before test
printer.Clean()
mockError := &model.AppError{
Message: "failed to get job",
@ -157,6 +160,70 @@ func (s *MmctlUnitTestSuite) TestComplianceExportShowCmdF() {
})
}
func (s *MmctlUnitTestSuite) TestComplianceExportCancelCmdF() {
s.Run("cancel job successfully", func() {
s.SetupTest() // Reset mocks before test
printer.Clean()
id := model.NewId()
s.client.
EXPECT().
CancelJob(context.TODO(), id).
Return(&model.Response{}, nil).
Times(1)
cmd := makeCmd()
err := complianceExportCancelCmdF(s.client, cmd, []string{id})
s.Require().Nil(err)
s.Len(printer.GetLines(), 0)
s.Len(printer.GetErrorLines(), 0)
})
s.Run("cancel job with get error", func() {
s.SetupTest() // Reset mocks before test
printer.Clean()
mockError := &model.AppError{
Message: "failed to get job",
}
s.client.
EXPECT().
CancelJob(context.TODO(), "invalid-job-id").
Return(&model.Response{}, mockError).
Times(1)
cmd := makeCmd()
err := complianceExportCancelCmdF(s.client, cmd, []string{"invalid-job-id"})
s.Require().NotNil(err)
s.EqualError(err, "failed to cancel compliance export job: failed to get job")
s.Len(printer.GetLines(), 0)
s.Len(printer.GetErrorLines(), 0)
})
s.Run("cancel job with cancel error", func() {
s.SetupTest() // Reset mocks before test
printer.Clean()
id := model.NewId()
mockError := &model.AppError{
Message: "failed to cancel job",
}
s.client.
EXPECT().
CancelJob(context.TODO(), id).
Return(&model.Response{}, mockError).
Times(1)
cmd := makeCmd()
err := complianceExportCancelCmdF(s.client, cmd, []string{id})
s.Require().NotNil(err)
s.EqualError(err, "failed to cancel compliance export job: failed to cancel job")
s.Len(printer.GetLines(), 0)
s.Len(printer.GetErrorLines(), 0)
})
}
func makeCmd() *cobra.Command {
cmd := &cobra.Command{}
cmd.Flags().Int("page", 0, "")

View file

@ -35,7 +35,7 @@ SEE ALSO
* `mmctl channel <mmctl_channel.rst>`_ - Management of channels
* `mmctl command <mmctl_command.rst>`_ - Management of slash commands
* `mmctl completion <mmctl_completion.rst>`_ - Generates autocompletion scripts for bash and zsh
* `mmctl compliance_export <mmctl_compliance_export.rst>`_ - Management of compliance exports
* `mmctl compliance-export <mmctl_compliance-export.rst>`_ - Management of compliance exports
* `mmctl config <mmctl_config.rst>`_ - Configuration
* `mmctl docs <mmctl_docs.rst>`_ - Generates mmctl documentation
* `mmctl export <mmctl_export.rst>`_ - Management of exports

View file

@ -1,6 +1,6 @@
.. _mmctl_compliance_export:
.. _mmctl_compliance-export:
mmctl compliance_export
mmctl compliance-export
-----------------------
Management of compliance exports
@ -16,7 +16,7 @@ Options
::
-h, --help help for compliance_export
-h, --help help for compliance-export
Options inherited from parent commands
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -37,6 +37,7 @@ SEE ALSO
~~~~~~~~
* `mmctl <mmctl.rst>`_ - Remote client for the Open Source, self-hosted Slack-alternative
* `mmctl compliance_export list <mmctl_compliance_export_list.rst>`_ - List compliance export jobs, sorted by creation date descending (newest first)
* `mmctl compliance_export show <mmctl_compliance_export_show.rst>`_ - Show compliance export job
* `mmctl compliance-export cancel <mmctl_compliance-export_cancel.rst>`_ - Cancel compliance export job
* `mmctl compliance-export list <mmctl_compliance-export_list.rst>`_ - List compliance export jobs, sorted by creation date descending (newest first)
* `mmctl compliance-export show <mmctl_compliance-export_show.rst>`_ - Show compliance export job

View file

@ -0,0 +1,51 @@
.. _mmctl_compliance-export_cancel:
mmctl compliance-export cancel
------------------------------
Cancel compliance export job
Synopsis
~~~~~~~~
Cancel compliance export job
::
mmctl compliance-export cancel [complianceExportJobID] [flags]
Examples
~~~~~~~~
::
compliance-export cancel o98rj3ur83dp5dppfyk5yk6osy
Options
~~~~~~~
::
-h, --help help for cancel
Options inherited from parent commands
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
::
--config string path to the configuration file (default "$XDG_CONFIG_HOME/mmctl/config")
--disable-pager disables paged output
--insecure-sha1-intermediate allows to use insecure TLS protocols, such as SHA-1
--insecure-tls-version allows to use TLS versions 1.0 and 1.1
--json the output format will be in json format
--local allows communicating with the server through a unix socket
--quiet prevent mmctl to generate output for the commands
--strict will only run commands if the mmctl version matches the server one
--suppress-warnings disables printing warning messages
SEE ALSO
~~~~~~~~
* `mmctl compliance-export <mmctl_compliance-export.rst>`_ - Management of compliance exports

View file

@ -1,6 +1,6 @@
.. _mmctl_compliance_export_list:
.. _mmctl_compliance-export_list:
mmctl compliance_export list
mmctl compliance-export list
----------------------------
List compliance export jobs, sorted by creation date descending (newest first)
@ -13,7 +13,7 @@ List compliance export jobs, sorted by creation date descending (newest first)
::
mmctl compliance_export list [flags]
mmctl compliance-export list [flags]
Options
~~~~~~~
@ -43,5 +43,5 @@ Options inherited from parent commands
SEE ALSO
~~~~~~~~
* `mmctl compliance_export <mmctl_compliance_export.rst>`_ - Management of compliance exports
* `mmctl compliance-export <mmctl_compliance-export.rst>`_ - Management of compliance exports

View file

@ -1,6 +1,6 @@
.. _mmctl_compliance_export_show:
.. _mmctl_compliance-export_show:
mmctl compliance_export show
mmctl compliance-export show
----------------------------
Show compliance export job
@ -13,14 +13,14 @@ Show compliance export job
::
mmctl compliance_export show [complianceExportJobID] [flags]
mmctl compliance-export show [complianceExportJobID] [flags]
Examples
~~~~~~~~
::
compliance_export show o98rj3ur83dp5dppfyk5yk6osy
compliance-export show o98rj3ur83dp5dppfyk5yk6osy
Options
~~~~~~~
@ -47,5 +47,5 @@ Options inherited from parent commands
SEE ALSO
~~~~~~~~
* `mmctl compliance_export <mmctl_compliance_export.rst>`_ - Management of compliance exports
* `mmctl compliance-export <mmctl_compliance-export.rst>`_ - Management of compliance exports