Doug Lauder
2023-03-22 17:22:27 -04:00
коммит произвёл GitHub
родитель b61c096497
Коммит c943ed6859
13276 изменённых файлов: 1695615 добавлений и 223189 удалений

202
server/playbooks/client/LICENSE Обычный файл
Просмотреть файл

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

63
server/playbooks/client/action.go Обычный файл
Просмотреть файл

@@ -0,0 +1,63 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package client
type GenericChannelActionWithoutPayload struct {
ID string `json:"id"`
ChannelID string `json:"channel_id"`
Enabled bool `json:"enabled"`
DeleteAt int64 `json:"delete_at"`
ActionType string `json:"action_type"`
TriggerType string `json:"trigger_type"`
}
type GenericChannelAction struct {
GenericChannelActionWithoutPayload
Payload interface{} `json:"payload"`
}
type WelcomeMessagePayload struct {
Message string `json:"message" mapstructure:"message"`
}
type PromptRunPlaybookFromKeywordsPayload struct {
Keywords []string `json:"keywords" mapstructure:"keywords"`
PlaybookID string `json:"playbook_id" mapstructure:"playbook_id"`
}
type CategorizeChannelPayload struct {
CategoryName string `json:"category_name" mapstructure:"category_name"`
}
type WelcomeMessageAction struct {
GenericChannelActionWithoutPayload
Payload WelcomeMessagePayload `json:"payload"`
}
const (
// Action types
ActionTypeWelcomeMessage = "send_welcome_message"
ActionTypePromptRunPlaybook = "prompt_run_playbook"
ActionTypeCategorizeChannel = "categorize_channel"
// Trigger types
TriggerTypeNewMemberJoins = "new_member_joins"
TriggerTypeKeywordsPosted = "keywords"
)
// ChannelActionListOptions specifies the optional parameters to the
// ActionsService.List method.
type ChannelActionListOptions struct {
TriggerType string `url:"trigger_type,omitempty"`
ActionType string `url:"action_type,omitempty"`
}
// ChannelActionCreateOptions specifies the parameters for ActionsService.Create method.
type ChannelActionCreateOptions struct {
ChannelID string `json:"channel_id"`
Enabled bool `json:"enabled"`
ActionType string `json:"action_type"`
TriggerType string `json:"trigger_type"`
Payload interface{} `json:"payload"`
}

78
server/playbooks/client/actions.go Обычный файл
Просмотреть файл

@@ -0,0 +1,78 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package client
import (
"context"
"fmt"
"net/http"
)
// ActionsService handles communication with the actions related
// methods of the Playbook API.
type ActionsService struct {
client *Client
}
// Create an action. Returns the id of the newly created action.
func (s *ActionsService) Create(ctx context.Context, channelID string, opts ChannelActionCreateOptions) (string, error) {
actionURL := fmt.Sprintf("actions/channels/%s", channelID)
req, err := s.client.newRequest(http.MethodPost, actionURL, opts)
if err != nil {
return "", err
}
var result struct {
ID string `json:"id"`
}
resp, err := s.client.do(ctx, req, &result)
if err != nil {
return "", err
}
resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
return "", fmt.Errorf("expected status code %d", http.StatusCreated)
}
return result.ID, nil
}
// List the actions in a channel.
func (s *ActionsService) List(ctx context.Context, channelID string, opts ChannelActionListOptions) ([]GenericChannelAction, error) {
actionURL, err := addOptions(fmt.Sprintf("actions/channels/%s", channelID), opts)
if err != nil {
return nil, fmt.Errorf("failed to build options: %w", err)
}
req, err := s.client.newRequest(http.MethodGet, actionURL, nil)
if err != nil {
return nil, fmt.Errorf("failed to build request: %w", err)
}
result := []GenericChannelAction{}
resp, err := s.client.do(ctx, req, &result)
if err != nil {
return nil, fmt.Errorf("failed to execute request: %w", err)
}
resp.Body.Close()
return result, nil
}
// Update an existing action.
func (s *ActionsService) Update(ctx context.Context, action GenericChannelAction) error {
updateURL := fmt.Sprintf("actions/channels/%s/%s", action.ChannelID, action.ID)
req, err := s.client.newRequest(http.MethodPut, updateURL, action)
if err != nil {
return err
}
_, err = s.client.do(ctx, req, nil)
if err != nil {
return err
}
return nil
}

276
server/playbooks/client/client.go Обычный файл
Просмотреть файл

@@ -0,0 +1,276 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package client
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"strconv"
"github.com/google/go-querystring/query"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/pkg/errors"
"golang.org/x/oauth2"
)
const (
apiVersion = "v0"
manifestID = "playbooks"
userAgent = "go-client/" + apiVersion
)
// Client manages communication with the Playbooks API.
type Client struct {
// client is the underlying HTTP client used to make API requests.
client *http.Client
// BaseURL is the base HTTP endpoint for the Playbooks plugin.
BaseURL *url.URL
// User agent used when communicating with the Playbooks API.
UserAgent string
// PlaybookRuns is a collection of methods used to interact with playbook runs.
PlaybookRuns *PlaybookRunService
// Playbooks is a collection of methods used to interact with playbooks.
Playbooks *PlaybooksService
// Settings is a collection of methods used to interact with settings.
Settings *SettingsService
// Actions is a collection of methods used to interact with actions.
Actions *ActionsService
// Stats is a collection of methods used to interact with stats.
Stats *StatsService
// Reminders is a collection of methods used to interact with reminders.
Reminders *RemindersService
// Telemetry is a collection of methods used to interact with telemetry.
Telemetry *TelemetryService
}
// New creates a new instance of Client using the configuration from the given Mattermost Client.
func New(client4 *model.Client4) (*Client, error) {
ctx := context.Background()
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: client4.AuthToken},
)
return newClient(client4.URL, oauth2.NewClient(ctx, ts))
}
// newClient creates a new instance of Client from the given URL and http.Client.
func newClient(mattermostSiteURL string, httpClient *http.Client) (*Client, error) {
siteURL, err := url.Parse(mattermostSiteURL)
if err != nil {
return nil, err
}
c := &Client{client: httpClient, BaseURL: siteURL, UserAgent: userAgent}
c.PlaybookRuns = &PlaybookRunService{c}
c.Playbooks = &PlaybooksService{c}
c.Settings = &SettingsService{c}
c.Actions = &ActionsService{c}
c.Stats = &StatsService{c}
c.Reminders = &RemindersService{c}
c.Telemetry = &TelemetryService{c}
return c, nil
}
// newRequest creates an API request, JSON-encoding any given body parameter.
func (c *Client) newRequest(method, endpoint string, body interface{}) (*http.Request, error) {
u, err := c.BaseURL.Parse(buildAPIURL(endpoint))
if err != nil {
return nil, errors.Wrapf(err, "invalid endpoint %s", endpoint)
}
var buf io.ReadWriter
if body != nil {
buf = &bytes.Buffer{}
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false)
err = enc.Encode(body)
if err != nil {
return nil, errors.Wrapf(err, "failed to encode body %s", body)
}
}
req, err := http.NewRequest(method, u.String(), buf)
if err != nil {
return nil, errors.Wrapf(err, "failed to create http request for url %s", u)
}
if buf != nil {
req.Header.Set("Content-Type", "application/json")
}
if c.UserAgent != "" {
req.Header.Set("User-Agent", c.UserAgent)
}
return req, nil
}
// buildAPIURL constructs the path to the given endpoint.
func buildAPIURL(endpoint string) string {
return fmt.Sprintf("plugins/%s/api/%s/%s", manifestID, apiVersion, endpoint)
}
// do sends an API request and returns the API response.
//
// The API response is JSON decoded and stored in the value pointed to by v, or returned as an
// error if an API error has occurred. If v implements the io.Writer
// interface, the raw response body will be written to v, without attempting to
// first decode it.
func (c *Client) do(ctx context.Context, req *http.Request, v interface{}) (*http.Response, error) {
if ctx == nil {
return nil, errors.New("context must be non-nil")
}
req = req.WithContext(ctx)
resp, err := c.client.Do(req)
if err != nil {
select {
case <-ctx.Done():
return nil, errors.Wrapf(ctx.Err(), "client err=%s", err.Error())
default:
}
return nil, err
}
defer resp.Body.Close()
err = checkResponse(resp)
if err != nil {
return resp, err
}
if v != nil {
if w, ok := v.(io.Writer); ok {
if _, err = io.Copy(w, resp.Body); err != nil {
return nil, err
}
} else {
body, _ := ioutil.ReadAll(resp.Body)
decErr := json.NewDecoder(bytes.NewReader(body)).Decode(v)
if decErr == io.EOF {
// TODO: Confirm if this happens only on empty bodies. If so, check that first before decoding.
decErr = nil // ignore EOF errors caused by empty response body
}
if decErr != nil {
err = decErr
}
}
}
return resp, err
}
type GraphQLInput struct {
Query string `json:"query"`
OperationName string `json:"operationName"`
Variables map[string]interface{} `json:"variables"`
}
func (c *Client) DoGraphql(ctx context.Context, input *GraphQLInput, v interface{}) error {
url := "query"
req, err := c.newRequest(http.MethodPost, url, input)
if err != nil {
return err
}
_, err = c.do(ctx, req, v)
if err != nil {
return err
}
return nil
}
// checkResponse checks the API response for an error.
//
// Any response with a status code outside 2xx is considered an error, and its body inspected for
// an optional `Error` property in a JSON struct.
func checkResponse(r *http.Response) error {
if c := r.StatusCode; http.StatusOK <= c && c <= 299 {
return nil
}
errorResponse := &ErrorResponse{
StatusCode: r.StatusCode,
Method: r.Request.Method,
URL: r.Request.URL.String(),
}
data, err := ioutil.ReadAll(r.Body)
if err != nil {
errorResponse.Err = fmt.Errorf("failed to read response body: %w", err)
}
r.Body = ioutil.NopCloser(bytes.NewBuffer(data))
if data != nil {
_ = json.Unmarshal(data, errorResponse)
}
return errorResponse
}
// addOption adds the given parameter as an URL query parameters to s.
func addOption(s string, name, value string) (string, error) {
u, err := url.Parse(s)
if err != nil {
return s, errors.Wrapf(err, "failed to parse %s", s)
}
qa := u.Query()
qa.Add(name, value)
u.RawQuery = qa.Encode()
return u.String(), nil
}
// addOptions adds the parameters in opts as URL query parameters to s. opts
// must be a struct whose fields may contain "url" tags.
func addOptions(s string, opts interface{}) (string, error) {
v := reflect.ValueOf(opts)
if v.Kind() == reflect.Ptr && v.IsNil() {
return s, nil
}
u, err := url.Parse(s)
if err != nil {
return s, errors.Wrapf(err, "failed to parse %s", s)
}
qs, err := query.Values(opts)
if err != nil {
return s, errors.Wrapf(err, "failed to opts %+v", opts)
}
// Append to the existing query parameters.
qa := u.Query()
for key, values := range qs {
for _, value := range values {
qa.Add(key, value)
}
}
u.RawQuery = qa.Encode()
return u.String(), nil
}
// addPaginationOptions adds the given pagination parameters as URL query parameters to s.
func addPaginationOptions(s string, page, perPage int) (string, error) {
u, err := url.Parse(s)
if err != nil {
return s, errors.Wrapf(err, "failed to parse %s", s)
}
qa := u.Query()
qa.Add("page", strconv.Itoa(page))
qa.Add("per_page", strconv.Itoa(perPage))
u.RawQuery = qa.Encode()
return u.String(), nil
}

5
server/playbooks/client/doc.go Обычный файл
Просмотреть файл

@@ -0,0 +1,5 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// Package client provides an HTTP client for using the Playbooks API.
package client

36
server/playbooks/client/doc_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,36 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package client_test
import (
"context"
"fmt"
"log"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/server/playbooks/client"
)
func Example() {
ctx := context.Background()
client4 := model.NewAPIv4Client("http://localhost:8065")
_, _, err := client4.Login("test@example.com", "testtest")
if err != nil {
log.Fatal(err)
}
c, err := client.New(client4)
if err != nil {
log.Fatal(err)
}
playbookRunID := "h4n3h7s1qjf5pkis4dn6cuxgwa"
playbookRun, err := c.PlaybookRuns.Get(ctx, playbookRunID)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Playbook Run Name: %s\n", playbookRun.Name)
}

52
server/playbooks/client/error_response.go Обычный файл
Просмотреть файл

@@ -0,0 +1,52 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package client
import (
"encoding/json"
"errors"
"fmt"
)
// ErrorResponse is an error from an API request.
type ErrorResponse struct {
// Method is the HTTP verb used in the API request.
Method string
// URL is the HTTP endpoint used in the API request.
URL string
// StatusCode is the HTTP status code returned by the API.
StatusCode int
// Err is the error parsed from the API response.
Err error `json:"error"`
}
func (e *ErrorResponse) UnmarshalJSON(data []byte) error {
type Alias ErrorResponse
temp := &struct {
Err string `json:"error"`
*Alias
}{
Alias: (*Alias)(e),
}
// Try to extract a structured error from the body, otherwise fall back to using
// the whole body as the error message.
if err := json.Unmarshal(data, &temp); err != nil || temp.Err == "" {
e.Err = errors.New(string(data))
} else {
e.Err = errors.New(temp.Err)
}
return nil
}
// Unwrap exposes the underlying error of an ErrorResponse.
func (e *ErrorResponse) Unwrap() error {
return e.Err
}
// Error describes the error from the API request.
func (e *ErrorResponse) Error() string {
return fmt.Sprintf("%s %s [%d]: %v", e.Method, e.URL, e.StatusCode, e.Err)
}

192
server/playbooks/client/playbook.go Обычный файл
Просмотреть файл

@@ -0,0 +1,192 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package client
import (
"fmt"
"gopkg.in/guregu/null.v4"
)
// Playbook represents the planning before a playbook run is initiated.
type Playbook struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Public bool `json:"public"`
TeamID string `json:"team_id"`
CreatePublicPlaybookRun bool `json:"create_public_playbook_run"`
CreateAt int64 `json:"create_at"`
DeleteAt int64 `json:"delete_at"`
NumStages int64 `json:"num_stages"`
NumSteps int64 `json:"num_steps"`
Checklists []Checklist `json:"checklists"`
Members []PlaybookMember `json:"members"`
ReminderMessageTemplate string `json:"reminder_message_template"`
ReminderTimerDefaultSeconds int64 `json:"reminder_timer_default_seconds"`
InvitedUserIDs []string `json:"invited_user_ids"`
InvitedGroupIDs []string `json:"invited_group_ids"`
InviteUsersEnabled bool `json:"invite_users_enabled"`
DefaultOwnerID string `json:"default_owner_id"`
DefaultOwnerEnabled bool `json:"default_owner_enabled"`
BroadcastChannelIDs []string `json:"broadcast_channel_ids"`
BroadcastEnabled bool `json:"broadcast_enabled"`
WebhookOnCreationURLs []string `json:"webhook_on_creation_urls"`
WebhookOnCreationEnabled bool `json:"webhook_on_creation_enabled"`
Metrics []PlaybookMetricConfig `json:"metrics"`
CreateChannelMemberOnNewParticipant bool `json:"create_channel_member_on_new_participant"`
RemoveChannelMemberOnRemovedParticipant bool `json:"remove_channel_member_on_removed_participant"`
ChannelID string `json:"channel_id" export:"channel_id"`
ChannelMode ChannelPlaybookMode `json:"channel_mode" export:"channel_mode"`
}
type PlaybookMember struct {
UserID string `json:"user_id"`
Roles []string `json:"roles"`
SchemeRoles []string `json:"scheme_roles"`
}
const (
MetricTypeDuration = "metric_duration"
MetricTypeCurrency = "metric_currency"
MetricTypeInteger = "metric_integer"
)
// Checklist represents a checklist in a playbook
type Checklist struct {
ID string `json:"id"`
Title string `json:"title"`
Items []ChecklistItem `json:"items"`
}
// ChecklistItem represents an item in a checklist
type ChecklistItem struct {
ID string `json:"id"`
Title string `json:"title"`
State string `json:"state"`
StateModified int64 `json:"state_modified"`
AssigneeID string `json:"assignee_id"`
AssigneeModified int64 `json:"assignee_modified"`
Command string `json:"command"`
CommandLastRun int64 `json:"command_last_run"`
Description string `json:"description"`
LastSkipped int64 `json:"delete_at"`
DueDate int64 `json:"due_date"`
TaskActions []TaskAction `json:"task_actions"`
}
// TaskAction represents a task action in an item
type TaskAction struct {
Trigger TriggerAction `json:"trigger"`
Actions []TriggerAction `json:"actions"`
}
// TriggerAction represents a trigger or action in a Task Action
type TriggerAction struct {
Type string `json:"type"`
Payload string `json:"payload"`
}
// PlaybookCreateOptions specifies the parameters for PlaybooksService.Create method.
type PlaybookCreateOptions struct {
Title string `json:"title"`
Description string `json:"description"`
TeamID string `json:"team_id"`
Public bool `json:"public"`
CreatePublicPlaybookRun bool `json:"create_public_playbook_run"`
Checklists []Checklist `json:"checklists"`
Members []PlaybookMember `json:"members"`
BroadcastChannelID string `json:"broadcast_channel_id"`
ReminderMessageTemplate string `json:"reminder_message_template"`
ReminderTimerDefaultSeconds int64 `json:"reminder_timer_default_seconds"`
InvitedUserIDs []string `json:"invited_user_ids"`
InvitedGroupIDs []string `json:"invited_group_ids"`
InviteUsersEnabled bool `json:"invite_users_enabled"`
DefaultOwnerID string `json:"default_owner_id"`
DefaultOwnerEnabled bool `json:"default_owner_enabled"`
BroadcastChannelIDs []string `json:"broadcast_channel_ids"`
BroadcastEnabled bool `json:"broadcast_enabled"`
Metrics []PlaybookMetricConfig `json:"metrics"`
CreateChannelMemberOnNewParticipant bool `json:"create_channel_member_on_new_participant"`
RemoveChannelMemberOnRemovedParticipant bool `json:"remove_channel_member_on_removed_participant"`
ChannelID string `json:"channel_id" export:"channel_id"`
ChannelMode ChannelPlaybookMode `json:"channel_mode" export:"channel_mode"`
}
type PlaybookMetricConfig struct {
ID string `json:"id"`
PlaybookID string `json:"playbook_id"`
Title string `json:"title"`
Description string `json:"description"`
Type string `json:"type"`
Target null.Int `json:"target"`
}
// PlaybookListOptions specifies the optional parameters to the
// PlaybooksService.List method.
type PlaybookListOptions struct {
Sort Sort `url:"sort,omitempty"`
Direction SortDirection `url:"direction,omitempty"`
SearchTeam string `url:"search_term,omitempty"`
WithArchived bool `url:"with_archived,omitempty"`
}
type GetPlaybooksResults struct {
TotalCount int `json:"total_count"`
PageCount int `json:"page_count"`
HasMore bool `json:"has_more"`
Items []Playbook `json:"items"`
}
type PlaybookStats struct {
RunsInProgress int `json:"runs_in_progress"`
ParticipantsActive int `json:"participants_active"`
RunsFinishedPrev30Days int `json:"runs_finished_prev_30_days"`
RunsFinishedPercentageChange int `json:"runs_finished_percentage_change"`
RunsStartedPerWeek []int `json:"runs_started_per_week"`
RunsStartedPerWeekTimes [][]int64 `json:"runs_started_per_week_times"`
ActiveRunsPerDay []int `json:"active_runs_per_day"`
ActiveRunsPerDayTimes [][]int64 `json:"active_runs_per_day_times"`
ActiveParticipantsPerDay []int `json:"active_participants_per_day"`
ActiveParticipantsPerDayTimes [][]int64 `json:"active_participants_per_day_times"`
MetricOverallAverage []null.Int `json:"metric_overall_average"`
MetricRollingAverage []null.Int `json:"metric_rolling_average"`
MetricRollingAverageChange []null.Int `json:"metric_rolling_average_change"`
MetricValueRange [][]int64 `json:"metric_value_range"`
MetricRollingValues [][]int64 `json:"metric_rolling_values"`
LastXRunNames []string `json:"last_x_run_names"`
}
type ChannelPlaybookMode int
const (
PlaybookRunCreateNewChannel ChannelPlaybookMode = iota
PlaybookRunLinkExistingChannel
)
var channelPlaybookTypes = [...]string{
PlaybookRunCreateNewChannel: "create_new_channel",
PlaybookRunLinkExistingChannel: "link_existing_channel",
}
// String creates the string version of the TelemetryTrack
func (cpm ChannelPlaybookMode) String() string {
return channelPlaybookTypes[cpm]
}
// MarshalText converts a ChannelPlaybookMode to a string for serializers (including JSON)
func (cpm ChannelPlaybookMode) MarshalText() ([]byte, error) {
return []byte(channelPlaybookTypes[cpm]), nil
}
// UnmarshalText parses a ChannelPlaybookMode from text. For deserializers (including JSON)
func (cpm *ChannelPlaybookMode) UnmarshalText(text []byte) error {
for i, st := range channelPlaybookTypes {
if st == string(text) {
*cpm = ChannelPlaybookMode(i)
return nil
}
}
return fmt.Errorf("unknown ChannelPlaybookMode: %s", string(text))
}

292
server/playbooks/client/playbook_run.go Обычный файл
Просмотреть файл

@@ -0,0 +1,292 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package client
import (
"time"
"gopkg.in/guregu/null.v4"
)
// Me is a constant that refers to the current user, and can be used in various APIs in place of
// explicitly specifying the current user's id.
const Me = "me"
// PlaybookRun represents a playbook run.
type PlaybookRun struct {
ID string `json:"id"`
Name string `json:"name"`
Summary string `json:"summary"`
SummaryModifiedAt int64 `json:"summary_modified_at"`
OwnerUserID string `json:"owner_user_id"`
ReporterUserID string `json:"reporter_user_id"`
TeamID string `json:"team_id"`
ChannelID string `json:"channel_id"`
CreateAt int64 `json:"create_at"`
EndAt int64 `json:"end_at"`
DeleteAt int64 `json:"delete_at"`
ActiveStage int `json:"active_stage"`
ActiveStageTitle string `json:"active_stage_title"`
PostID string `json:"post_id"`
PlaybookID string `json:"playbook_id"`
Checklists []Checklist `json:"checklists"`
StatusPosts []StatusPost `json:"status_posts"`
CurrentStatus string `json:"current_status"`
LastStatusUpdateAt int64 `json:"last_status_update_at"`
ReminderPostID string `json:"reminder_post_id"`
PreviousReminder time.Duration `json:"previous_reminder"`
ReminderTimerDefaultSeconds int64 `json:"reminder_timer_default_seconds"`
StatusUpdateEnabled bool `json:"status_update_enabled"`
BroadcastChannelIDs []string `json:"broadcast_channel_ids"`
WebhookOnStatusUpdateURLs []string `json:"webhook_on_status_update_urls"`
StatusUpdateBroadcastChannelsEnabled bool `json:"status_update_broadcast_channels_enabled"`
StatusUpdateBroadcastWebhooksEnabled bool `json:"status_update_broadcast_webhooks_enabled"`
ReminderMessageTemplate string `json:"reminder_message_template"`
InvitedUserIDs []string `json:"invited_user_ids"`
InvitedGroupIDs []string `json:"invited_group_ids"`
TimelineEvents []TimelineEvent `json:"timeline_events"`
DefaultOwnerID string `json:"default_owner_id"`
WebhookOnCreationURLs []string `json:"webhook_on_creation_urls"`
Retrospective string `json:"retrospective"`
RetrospectivePublishedAt int64 `json:"retrospective_published_at"`
RetrospectiveWasCanceled bool `json:"retrospective_was_canceled"`
RetrospectiveReminderIntervalSeconds int64 `json:"retrospective_reminder_interval_seconds"`
RetrospectiveEnabled bool `json:"retrospective_enabled"`
MessageOnJoin string `json:"message_on_join"`
ParticipantIDs []string `json:"participant_ids"`
CategoryName string `json:"category_name"`
MetricsData []RunMetricData `json:"metrics_data"`
CreateChannelMemberOnNewParticipant bool `json:"create_channel_member_on_new_participant"`
RemoveChannelMemberOnRemovedParticipant bool `json:"remove_channel_member_on_removed_participant"`
}
// StatusPost is information added to the playbook run when selecting from the db and sent to the
// client; it is not saved to the db.
type StatusPost struct {
ID string `json:"id"`
CreateAt int64 `json:"create_at"`
DeleteAt int64 `json:"delete_at"`
}
// StatusPostComplete is the complete status update (post)
// it's similar to StatusPost but with extended info.
type StatusPostComplete struct {
Id string `json:"id"`
CreateAt int64 `json:"create_at"`
UpdateAt int64 `json:"update_at"`
DeleteAt int64 `json:"delete_at"`
Message string `json:"message"`
AuthorUserName string `json:"author_user_name"`
}
// Metadata tracks ancillary metadata about a playbook run.
type Metadata struct {
ChannelName string `json:"channel_name"`
ChannelDisplayName string `json:"channel_display_name"`
TeamName string `json:"team_name"`
NumParticipants int64 `json:"num_participants"`
TotalPosts int64 `json:"total_posts"`
Followers []string `json:"followers"`
}
// TimelineEventType describes a type of timeline event.
type TimelineEventType string
const (
PlaybookRunCreated TimelineEventType = "incident_created"
TaskStateModified TimelineEventType = "task_state_modified"
StatusUpdated TimelineEventType = "status_updated"
StatusUpdateRequested TimelineEventType = "status_update_requested"
OwnerChanged TimelineEventType = "owner_changed"
AssigneeChanged TimelineEventType = "assignee_changed"
RanSlashCommand TimelineEventType = "ran_slash_command"
EventFromPost TimelineEventType = "event_from_post"
UserJoinedLeft TimelineEventType = "user_joined_left"
PublishedRetrospective TimelineEventType = "published_retrospective"
CanceledRetrospective TimelineEventType = "canceled_retrospective"
RunFinished TimelineEventType = "run_finished"
RunRestored TimelineEventType = "run_restored"
StatusUpdatesEnabled TimelineEventType = "status_updates_enabled"
StatusUpdatesDisabled TimelineEventType = "status_updates_disabled"
)
// TimelineEvent represents an event recorded to a playbook run's timeline.
type TimelineEvent struct {
ID string `json:"id"`
PlaybookRunID string `json:"playbook_run"`
CreateAt int64 `json:"create_at"`
DeleteAt int64 `json:"delete_at"`
EventAt int64 `json:"event_at"`
EventType TimelineEventType `json:"event_type"`
Summary string `json:"summary"`
Details string `json:"details"`
PostID string `json:"post_id"`
SubjectUserID string `json:"subject_user_id"`
CreatorUserID string `json:"creator_user_id"`
}
// PlaybookRunCreateOptions specifies the parameters for PlaybookRunService.Create method.
type PlaybookRunCreateOptions struct {
Name string `json:"name"`
OwnerUserID string `json:"owner_user_id"`
TeamID string `json:"team_id"`
ChannelID string `json:"channel_id"`
Description string `json:"description"`
PostID string `json:"post_id"`
PlaybookID string `json:"playbook_id"`
CreatePublicRun *bool `json:"create_public_run"`
Type string `json:"type"`
}
// RunAction represents the run action settings. Frontend passes this struct to update settings.
type RunAction struct {
BroadcastChannelIDs []string `json:"broadcast_channel_ids"`
WebhookOnStatusUpdateURLs []string `json:"webhook_on_status_update_urls"`
StatusUpdateBroadcastChannelsEnabled bool `json:"status_update_broadcast_channels_enabled"`
StatusUpdateBroadcastWebhooksEnabled bool `json:"status_update_broadcast_webhooks_enabled"`
}
// RetrospectiveUpdate represents the run retrospective info
type RetrospectiveUpdate struct {
Text string `json:"retrospective"`
Metrics []RunMetricData `json:"metrics"`
}
// Sort enumerates the available fields we can sort on.
type Sort string
const (
// SortByCreateAt sorts by the "create_at" field. It is the default.
SortByCreateAt Sort = "create_at"
// SortByID sorts by the "id" field.
SortByID Sort = "id"
// SortByName sorts by the "name" field.
SortByName Sort = "name"
// SortByOwnerUserID sorts by the "owner_user_id" field.
SortByOwnerUserID Sort = "owner_user_id"
// SortByTeamID sorts by the "team_id" field.
SortByTeamID Sort = "team_id"
// SortByEndAt sorts by the "end_at" field.
SortByEndAt Sort = "end_at"
// SortBySteps sorts playbooks by the number of steps in the playbook.
SortBySteps Sort = "steps"
// SortByStages sorts playbooks by the number of stages in the playbook.
SortByStages Sort = "stages"
// SortByTitle sorts by the "title" field.
SortByTitle Sort = "title"
// SortByRuns sorts by the number of times a playbook has been run.
SortByRuns Sort = "runs"
)
// SortDirection determines whether results are sorted ascending or descending.
type SortDirection string
const (
// Desc sorts the results in descending order.
SortDesc SortDirection = "desc"
// Asc sorts the results in ascending order.
SortAsc SortDirection = "asc"
)
// PlaybookRunListOptions specifies the optional parameters to the
// PlaybookRunService.List method.
type PlaybookRunListOptions struct {
// TeamID filters playbook runs to those in the given team.
TeamID string `url:"team_id,omitempty"`
Sort Sort `url:"sort,omitempty"`
Direction SortDirection `url:"direction,omitempty"`
// Statuses filters by InProgress or Ended; defaults to All when no status specified.
Statuses []Status `url:"statuses,omitempty"`
// OwnerID filters by owner's Mattermost user ID. Defaults to blank (no filter). Specify "me" for current user.
OwnerID string `url:"owner_user_id,omitempty"`
// ParticipantID filters playbook runs that have this user as a participant. Defaults to blank (no filter). Specify "me" for current user.
ParticipantID string `url:"participant_id,omitempty"`
// ParticipantOrFollowerID filters playbook runs that have this user as member or as follower. Defaults to blank (no filter). Specify "me" for current user.
ParticipantOrFollowerID string `url:"participant_or_follower,omitempty"`
// SearchTerm returns results of the search term and respecting the other header filter options.
// The search term acts as a filter and respects the Sort and Direction fields (i.e., results are
// not returned in relevance order).
SearchTerm string `url:"search_term,omitempty"`
// PlaybookID filters playbook runs that are derived from this playbook id.
// Defaults to blank (no filter).
PlaybookID string `url:"playbook_id,omitempty"`
// ActiveGTE filters playbook runs that were active after (or equal) to the unix time given (in millis).
// A value of 0 means the filter is ignored (which is the default).
ActiveGTE int64 `url:"active_gte,omitempty"`
// ActiveLT filters playbook runs that were active before the unix time given (in millis).
// A value of 0 means the filter is ignored (which is the default).
ActiveLT int64 `url:"active_lt,omitempty"`
// StartedGTE filters playbook runs that were started after (or equal) to the unix time given (in millis).
// A value of 0 means the filter is ignored (which is the default).
StartedGTE int64 `url:"started_gte,omitempty"`
// StartedLT filters playbook runs that were started before the unix time given (in millis).
// A value of 0 means the filter is ignored (which is the default).
StartedLT int64 `url:"started_lt,omitempty"`
}
// PlaybookRunList contains the paginated result.
type PlaybookRunList struct {
TotalCount int `json:"total_count"`
PageCount int `json:"page_count"`
HasMore bool `json:"has_more"`
Items []*PlaybookRun
}
// Status is the type used to specify the activity status of the playbook run.
type Status string
const (
StatusInProgress Status = "InProgress"
StatusFinished Status = "Finished"
)
type GetPlaybookRunsResults struct {
TotalCount int `json:"total_count"`
PageCount int `json:"page_count"`
HasMore bool `json:"has_more"`
Items []PlaybookRun `json:"items"`
}
// StatusUpdateOptions are the fields required to update a playbook run's status
type StatusUpdateOptions struct {
Message string `json:"message"`
Reminder time.Duration `json:"reminder"`
FinishRun bool `json:"finish_run"`
}
type RunMetricData struct {
MetricConfigID string `json:"metric_config_id"`
Value null.Int `json:"value"`
}
// OwnerInfo holds the summary information of a owner.
type OwnerInfo struct {
UserID string `json:"user_id"`
Username string `json:"username"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Nickname string `json:"nickname"`
}

375
server/playbooks/client/playbook_runs.go Обычный файл
Просмотреть файл

@@ -0,0 +1,375 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package client
import (
"context"
"fmt"
"net/http"
"time"
)
// PlaybookRunService handles communication with the playbook run related
// methods of the Playbooks API.
type PlaybookRunService struct {
client *Client
}
// Get a playbook run.
func (s *PlaybookRunService) Get(ctx context.Context, playbookRunID string) (*PlaybookRun, error) {
playbookRunURL := fmt.Sprintf("runs/%s", playbookRunID)
req, err := s.client.newRequest(http.MethodGet, playbookRunURL, nil)
if err != nil {
return nil, err
}
playbookRun := new(PlaybookRun)
resp, err := s.client.do(ctx, req, playbookRun)
if err != nil {
return nil, err
}
resp.Body.Close()
return playbookRun, nil
}
// GetByChannelID gets a playbook run by ChannelID.
func (s *PlaybookRunService) GetByChannelID(ctx context.Context, channelID string) (*PlaybookRun, error) {
channelURL := fmt.Sprintf("runs/channel/%s", channelID)
req, err := s.client.newRequest(http.MethodGet, channelURL, nil)
if err != nil {
return nil, err
}
playbookRun := new(PlaybookRun)
resp, err := s.client.do(ctx, req, playbookRun)
if err != nil {
return nil, err
}
resp.Body.Close()
return playbookRun, nil
}
// Get a playbook run's metadata.
func (s *PlaybookRunService) GetMetadata(ctx context.Context, playbookRunID string) (*Metadata, error) {
playbookRunURL := fmt.Sprintf("runs/%s/metadata", playbookRunID)
req, err := s.client.newRequest(http.MethodGet, playbookRunURL, nil)
if err != nil {
return nil, err
}
playbookRun := new(Metadata)
resp, err := s.client.do(ctx, req, playbookRun)
if err != nil {
return nil, err
}
resp.Body.Close()
return playbookRun, nil
}
// Get all playbook status updates.
func (s *PlaybookRunService) GetStatusUpdates(ctx context.Context, playbookRunID string) ([]StatusPostComplete, error) {
playbookRunURL := fmt.Sprintf("runs/%s/status-updates", playbookRunID)
req, err := s.client.newRequest(http.MethodGet, playbookRunURL, nil)
if err != nil {
return nil, err
}
var statusUpdates []StatusPostComplete
resp, err := s.client.do(ctx, req, &statusUpdates)
if err != nil {
return nil, err
}
resp.Body.Close()
return statusUpdates, nil
}
// List the playbook runs.
func (s *PlaybookRunService) List(ctx context.Context, page, perPage int, opts PlaybookRunListOptions) (*GetPlaybookRunsResults, error) {
playbookRunURL := "runs"
playbookRunURL, err := addOptions(playbookRunURL, opts)
if err != nil {
return nil, fmt.Errorf("failed to build options: %w", err)
}
playbookRunURL, err = addPaginationOptions(playbookRunURL, page, perPage)
if err != nil {
return nil, fmt.Errorf("failed to build pagination options: %w", err)
}
req, err := s.client.newRequest(http.MethodGet, playbookRunURL, nil)
if err != nil {
return nil, fmt.Errorf("failed to build request: %w", err)
}
result := &GetPlaybookRunsResults{}
resp, err := s.client.do(ctx, req, result)
if err != nil {
return nil, fmt.Errorf("failed to execute request: %w", err)
}
resp.Body.Close()
return result, nil
}
// Create a playbook run.
func (s *PlaybookRunService) Create(ctx context.Context, opts PlaybookRunCreateOptions) (*PlaybookRun, error) {
playbookRunURL := "runs"
req, err := s.client.newRequest(http.MethodPost, playbookRunURL, opts)
if err != nil {
return nil, err
}
playbookRun := new(PlaybookRun)
resp, err := s.client.do(ctx, req, playbookRun)
if err != nil {
return nil, err
}
resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
return nil, fmt.Errorf("expected status code %d", http.StatusCreated)
}
return playbookRun, nil
}
func (s *PlaybookRunService) UpdateStatus(ctx context.Context, playbookRunID string, message string, reminderInSeconds int64) error {
updateURL := fmt.Sprintf("runs/%s/status", playbookRunID)
opts := StatusUpdateOptions{
Message: message,
Reminder: time.Duration(reminderInSeconds),
}
req, err := s.client.newRequest(http.MethodPost, updateURL, opts)
if err != nil {
return err
}
resp, err := s.client.do(ctx, req, nil)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("expected status code %d", http.StatusOK)
}
return nil
}
func (s *PlaybookRunService) RequestUpdate(ctx context.Context, playbookRunID, userID string) error {
requestURL := fmt.Sprintf("runs/%s/request-update", playbookRunID)
req, err := s.client.newRequest(http.MethodPost, requestURL, nil)
if err != nil {
return err
}
resp, err := s.client.do(ctx, req, nil)
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("expected status code %d", http.StatusOK)
}
return err
}
func (s *PlaybookRunService) Finish(ctx context.Context, playbookRunID string) error {
finishURL := fmt.Sprintf("runs/%s/finish", playbookRunID)
req, err := s.client.newRequest(http.MethodPut, finishURL, nil)
if err != nil {
return err
}
_, err = s.client.do(ctx, req, nil)
if err != nil {
return err
}
return nil
}
func (s *PlaybookRunService) CreateChecklist(ctx context.Context, playbookRunID string, checklist Checklist) error {
createURL := fmt.Sprintf("runs/%s/checklists", playbookRunID)
req, err := s.client.newRequest(http.MethodPost, createURL, checklist)
if err != nil {
return err
}
_, err = s.client.do(ctx, req, nil)
return err
}
func (s *PlaybookRunService) RemoveChecklist(ctx context.Context, playbookRunID string, checklistNumber int) error {
createURL := fmt.Sprintf("runs/%s/checklists/%d", playbookRunID, checklistNumber)
req, err := s.client.newRequest(http.MethodDelete, createURL, nil)
if err != nil {
return err
}
_, err = s.client.do(ctx, req, nil)
return err
}
func (s *PlaybookRunService) RenameChecklist(ctx context.Context, playbookRunID string, checklistNumber int, newTitle string) error {
createURL := fmt.Sprintf("runs/%s/checklists/%d/rename", playbookRunID, checklistNumber)
req, err := s.client.newRequest(http.MethodPut, createURL, struct{ Title string }{newTitle})
if err != nil {
return err
}
_, err = s.client.do(ctx, req, nil)
return err
}
func (s *PlaybookRunService) AddChecklistItem(ctx context.Context, playbookRunID string, checklistNumber int, checklistItem ChecklistItem) error {
addURL := fmt.Sprintf("runs/%s/checklists/%d/add", playbookRunID, checklistNumber)
req, err := s.client.newRequest(http.MethodPost, addURL, checklistItem)
if err != nil {
return err
}
_, err = s.client.do(ctx, req, nil)
return err
}
func (s *PlaybookRunService) MoveChecklist(ctx context.Context, playbookRunID string, sourceChecklistIdx, destChecklistIdx int) error {
createURL := fmt.Sprintf("runs/%s/checklists/move", playbookRunID)
body := struct {
SourceChecklistIdx int `json:"source_checklist_idx"`
DestChecklistIdx int `json:"dest_checklist_idx"`
}{sourceChecklistIdx, destChecklistIdx}
req, err := s.client.newRequest(http.MethodPost, createURL, body)
if err != nil {
return err
}
_, err = s.client.do(ctx, req, nil)
return err
}
func (s *PlaybookRunService) MoveChecklistItem(ctx context.Context, playbookRunID string, sourceChecklistIdx, sourceItemIdx, destChecklistIdx, destItemIdx int) error {
createURL := fmt.Sprintf("runs/%s/checklists/move-item", playbookRunID)
body := struct {
SourceChecklistIdx int `json:"source_checklist_idx"`
SourceItemIdx int `json:"source_item_idx"`
DestChecklistIdx int `json:"dest_checklist_idx"`
DestItemIdx int `json:"dest_item_idx"`
}{sourceChecklistIdx, sourceItemIdx, destChecklistIdx, destItemIdx}
req, err := s.client.newRequest(http.MethodPost, createURL, body)
if err != nil {
return err
}
_, err = s.client.do(ctx, req, nil)
return err
}
// UpdateRetrospective updates the run's retrospective info
func (s *PlaybookRunService) UpdateRetrospective(ctx context.Context, playbookRunID, userID string, retroUpdate RetrospectiveUpdate) error {
createURL := fmt.Sprintf("runs/%s/retrospective", playbookRunID)
req, err := s.client.newRequest(http.MethodPost, createURL, retroUpdate)
if err != nil {
return err
}
resp, err := s.client.do(ctx, req, nil)
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("expected status code %d", http.StatusOK)
}
return err
}
// PublishRetrospective publishes the run's retrospective
func (s *PlaybookRunService) PublishRetrospective(ctx context.Context, playbookRunID, userID string, retroUpdate RetrospectiveUpdate) error {
createURL := fmt.Sprintf("runs/%s/retrospective/publish", playbookRunID)
req, err := s.client.newRequest(http.MethodPost, createURL, retroUpdate)
if err != nil {
return err
}
resp, err := s.client.do(ctx, req, nil)
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("expected status code %d", http.StatusOK)
}
return err
}
func (s *PlaybookRunService) SetItemAssignee(ctx context.Context, playbookRunID string, checklistIdx int, itemIdx int, assigneeID string) error {
createURL := fmt.Sprintf("runs/%s/checklists/%d/item/%d/assignee", playbookRunID, checklistIdx, itemIdx)
body := struct {
AssigneeID string `json:"assignee_id"`
}{assigneeID}
req, err := s.client.newRequest(http.MethodPut, createURL, body)
if err != nil {
return err
}
_, err = s.client.do(ctx, req, nil)
return err
}
func (s *PlaybookRunService) SetItemCommand(ctx context.Context, playbookRunID string, checklistIdx int, itemIdx int, newCommand string) error {
createURL := fmt.Sprintf("runs/%s/checklists/%d/item/%d/command", playbookRunID, checklistIdx, itemIdx)
body := struct {
Command string `json:"command"`
}{newCommand}
req, err := s.client.newRequest(http.MethodPut, createURL, body)
if err != nil {
return err
}
_, err = s.client.do(ctx, req, nil)
return err
}
func (s *PlaybookRunService) RunItemCommand(ctx context.Context, playbookRunID string, checklistIdx int, itemIdx int) error {
createURL := fmt.Sprintf("runs/%s/checklists/%d/item/%d/run", playbookRunID, checklistIdx, itemIdx)
req, err := s.client.newRequest(http.MethodPost, createURL, nil)
if err != nil {
return err
}
_, err = s.client.do(ctx, req, nil)
return err
}
func (s *PlaybookRunService) SetItemDueDate(ctx context.Context, playbookRunID string, checklistIdx int, itemIdx int, duedate int64) error {
createURL := fmt.Sprintf("runs/%s/checklists/%d/item/%d/duedate", playbookRunID, checklistIdx, itemIdx)
body := struct {
DueDate int64 `json:"due_date"`
}{duedate}
req, err := s.client.newRequest(http.MethodPut, createURL, body)
if err != nil {
return err
}
_, err = s.client.do(ctx, req, nil)
return err
}
// Get a playbook run.
func (s *PlaybookRunService) GetOwners(ctx context.Context) ([]OwnerInfo, error) {
req, err := s.client.newRequest(http.MethodGet, "runs/owners", nil)
if err != nil {
return nil, err
}
owners := make([]OwnerInfo, 0)
resp, err := s.client.do(ctx, req, &owners)
if err != nil {
return nil, err
}
resp.Body.Close()
return owners, nil
}

Просмотреть файл

@@ -0,0 +1,77 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package client_test
import (
"context"
"fmt"
"log"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/server/playbooks/client"
)
func ExamplePlaybookRunService_Get() {
ctx := context.Background()
client4 := model.NewAPIv4Client("http://localhost:8065")
client4.Login("test@example.com", "testtest")
c, err := client.New(client4)
if err != nil {
log.Fatal(err)
}
playbookRunID := "h4n3h7s1qjf5pkis4dn6cuxgwa"
playbookRun, err := c.PlaybookRuns.Get(ctx, playbookRunID)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Playbook Run Name: %s\n", playbookRun.Name)
}
func ExamplePlaybookRunService_List() {
ctx := context.Background()
client4 := model.NewAPIv4Client("http://localhost:8065")
_, _, err := client4.Login("test@example.com", "testtest")
if err != nil {
log.Fatal(err.Error())
}
teams, _, err := client4.GetAllTeams("", 0, 1)
if err != nil {
log.Fatal(err.Error())
}
if len(teams) == 0 {
log.Fatal("no teams for this user")
}
c, err := client.New(client4)
if err != nil {
log.Fatal(err)
}
var playbookRuns []client.PlaybookRun
for page := 0; ; page++ {
result, err := c.PlaybookRuns.List(ctx, page, 100, client.PlaybookRunListOptions{
TeamID: teams[0].Id,
Sort: client.SortByCreateAt,
Direction: client.SortDesc,
})
if err != nil {
log.Fatal(err)
}
playbookRuns = append(playbookRuns, result.Items...)
if !result.HasMore {
break
}
}
for _, playbookRun := range playbookRuns {
fmt.Printf("Playbook Run Name: %s\n", playbookRun.Name)
}
}

269
server/playbooks/client/playbooks.go Обычный файл
Просмотреть файл

@@ -0,0 +1,269 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package client
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"net/http"
"github.com/pkg/errors"
)
// PlaybooksService handles communication with the playbook related
// methods of the Playbook API.
type PlaybooksService struct {
client *Client
}
// Get a playbook.
func (s *PlaybooksService) Get(ctx context.Context, playbookID string) (*Playbook, error) {
playbookURL := fmt.Sprintf("playbooks/%s", playbookID)
req, err := s.client.newRequest(http.MethodGet, playbookURL, nil)
if err != nil {
return nil, err
}
playbook := new(Playbook)
resp, err := s.client.do(ctx, req, playbook)
if err != nil {
return nil, err
}
resp.Body.Close()
return playbook, nil
}
// List the playbooks.
func (s *PlaybooksService) List(ctx context.Context, teamId string, page, perPage int, opts PlaybookListOptions) (*GetPlaybooksResults, error) {
playbookURL := "playbooks"
playbookURL, err := addOption(playbookURL, "team_id", teamId)
if err != nil {
return nil, fmt.Errorf("failed to build options: %w", err)
}
playbookURL, err = addPaginationOptions(playbookURL, page, perPage)
if err != nil {
return nil, fmt.Errorf("failed to build pagination options: %w", err)
}
playbookURL, err = addOptions(playbookURL, opts)
if err != nil {
return nil, fmt.Errorf("failed to build options: %w", err)
}
req, err := s.client.newRequest(http.MethodGet, playbookURL, nil)
if err != nil {
return nil, fmt.Errorf("failed to build request: %w", err)
}
result := &GetPlaybooksResults{}
resp, err := s.client.do(ctx, req, result)
if err != nil {
return nil, fmt.Errorf("failed to execute request: %w", err)
}
resp.Body.Close()
return result, nil
}
// Create a playbook. Returns the id of the newly created playbook
func (s *PlaybooksService) Create(ctx context.Context, opts PlaybookCreateOptions) (string, error) {
// For ease of use set the default if not specificed so it doesn't just error
if opts.ReminderTimerDefaultSeconds == 0 {
opts.ReminderTimerDefaultSeconds = 86400
}
playbookURL := "playbooks"
req, err := s.client.newRequest(http.MethodPost, playbookURL, opts)
if err != nil {
return "", err
}
var result struct {
ID string `json:"id"`
}
resp, err := s.client.do(ctx, req, &result)
if err != nil {
return "", err
}
resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
return "", fmt.Errorf("expected status code %d", http.StatusCreated)
}
return result.ID, nil
}
func (s *PlaybooksService) Update(ctx context.Context, playbook Playbook) error {
updateURL := fmt.Sprintf("playbooks/%s", playbook.ID)
req, err := s.client.newRequest(http.MethodPut, updateURL, playbook)
if err != nil {
return err
}
_, err = s.client.do(ctx, req, nil)
if err != nil {
return err
}
return nil
}
func (s *PlaybooksService) Archive(ctx context.Context, playbookID string) error {
updateURL := fmt.Sprintf("playbooks/%s", playbookID)
req, err := s.client.newRequest(http.MethodDelete, updateURL, nil)
if err != nil {
return err
}
_, err = s.client.do(ctx, req, nil)
if err != nil {
return err
}
return nil
}
func (s *PlaybooksService) Export(ctx context.Context, playbookID string) ([]byte, error) {
url := fmt.Sprintf("playbooks/%s/export", playbookID)
req, err := s.client.newRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
resp, err := s.client.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
result, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("expected status code %d", http.StatusOK)
}
return result, nil
}
// Duplicate a playbook. Returns the id of the newly created playbook
func (s *PlaybooksService) Duplicate(ctx context.Context, playbookID string) (string, error) {
url := fmt.Sprintf("playbooks/%s/duplicate", playbookID)
req, err := s.client.newRequest(http.MethodPost, url, nil)
if err != nil {
return "", err
}
var result struct {
ID string `json:"id"`
}
resp, err := s.client.do(ctx, req, &result)
if err != nil {
return "", err
}
resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
return "", fmt.Errorf("expected status code %d", http.StatusCreated)
}
return result.ID, nil
}
// Imports a playbook. Returns the id of the newly created playbook
func (s *PlaybooksService) Import(ctx context.Context, toImport []byte, team string) (string, error) {
url := "playbooks/import?team_id=" + team
u, err := s.client.BaseURL.Parse(buildAPIURL(url))
if err != nil {
return "", errors.Wrapf(err, "invalid endpoint %s", url)
}
req, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewReader(toImport))
if err != nil {
return "", errors.Wrapf(err, "failed to create http request for import")
}
req.Header.Set("Content-Type", "application/json")
var result struct {
ID string `json:"id"`
}
resp, err := s.client.do(ctx, req, &result)
if err != nil {
return "", err
}
resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
return "", fmt.Errorf("expected status code %d", http.StatusCreated)
}
return result.ID, nil
}
func (s *PlaybooksService) Stats(ctx context.Context, playbookID string) (*PlaybookStats, error) {
playbookStatsURL := fmt.Sprintf("stats/playbook?playbook_id=%s", playbookID)
req, err := s.client.newRequest(http.MethodGet, playbookStatsURL, nil)
if err != nil {
return nil, err
}
stats := new(PlaybookStats)
resp, err := s.client.do(ctx, req, stats)
if err != nil {
return nil, err
}
resp.Body.Close()
return stats, nil
}
func (s *PlaybooksService) AutoFollow(ctx context.Context, playbookID string, userID string) error {
followsURL := fmt.Sprintf("playbooks/%s/autofollows/%s", playbookID, userID)
req, err := s.client.newRequest(http.MethodPut, followsURL, nil)
if err != nil {
return err
}
_, err = s.client.do(ctx, req, nil)
if err != nil {
return err
}
return nil
}
func (s *PlaybooksService) AutoUnfollow(ctx context.Context, playbookID string, userID string) error {
followsURL := fmt.Sprintf("playbooks/%s/autofollows/%s", playbookID, userID)
req, err := s.client.newRequest(http.MethodDelete, followsURL, nil)
if err != nil {
return err
}
_, err = s.client.do(ctx, req, nil)
if err != nil {
return err
}
return nil
}
func (s *PlaybooksService) GetAutoFollows(ctx context.Context, playbookID string) ([]string, error) {
autofollowsURL := fmt.Sprintf("playbooks/%s/autofollows", playbookID)
req, err := s.client.newRequest(http.MethodGet, autofollowsURL, nil)
if err != nil {
return nil, err
}
var followers []string
resp, err := s.client.do(ctx, req, &followers)
if err != nil {
return nil, err
}
resp.Body.Close()
return followers, nil
}

76
server/playbooks/client/playbooks_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,76 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package client_test
import (
"context"
"fmt"
"log"
"github.com/mattermost/mattermost-server/v6/model"
"github.com/mattermost/mattermost-server/v6/server/playbooks/client"
)
func ExamplePlaybooksService_Get() {
ctx := context.Background()
client4 := model.NewAPIv4Client("http://localhost:8065")
client4.Login("test@example.com", "testtest")
c, err := client.New(client4)
if err != nil {
log.Fatal(err)
}
playbookID := "h4n3h7s1qjf5pkis4dn6cuxgwa"
playbook, err := c.Playbooks.Get(ctx, playbookID)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Playbook Name: %s\n", playbook.Title)
}
func ExamplePlaybooksService_List() {
ctx := context.Background()
client4 := model.NewAPIv4Client("http://localhost:8065")
_, _, err := client4.Login("test@example.com", "testtest")
if err != nil {
log.Fatal(err.Error())
}
teams, _, err := client4.GetAllTeams("", 0, 1)
if err != nil {
log.Fatal(err.Error())
}
if len(teams) == 0 {
log.Fatal("no teams for this user")
}
c, err := client.New(client4)
if err != nil {
log.Fatal(err)
}
var playbooks []client.Playbook
for page := 0; ; page++ {
result, err := c.Playbooks.List(ctx, teams[0].Id, page, 100, client.PlaybookListOptions{
Sort: client.SortByCreateAt,
Direction: client.SortDesc,
})
if err != nil {
log.Fatal(err)
}
playbooks = append(playbooks, result.Items...)
if !result.HasMore {
break
}
}
for _, playbook := range playbooks {
fmt.Printf("Playbook Name: %s\n", playbook.Title)
}
}

8
server/playbooks/client/reminder.go Обычный файл
Просмотреть файл

@@ -0,0 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package client
type ReminderResetPayload struct {
NewReminderSeconds int `json:"new_reminder_seconds"`
}

36
server/playbooks/client/reminders.go Обычный файл
Просмотреть файл

@@ -0,0 +1,36 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package client
import (
"context"
"fmt"
"io/ioutil"
"net/http"
)
type RemindersService struct {
client *Client
}
func (s *RemindersService) Reset(ctx context.Context, playbookRunID string, payload ReminderResetPayload) error {
resetURL := fmt.Sprintf("runs/%s/reminder", playbookRunID)
req, err := s.client.newRequest(http.MethodPost, resetURL, payload)
if err != nil {
return err
}
resp, err := s.client.do(ctx, req, ioutil.Discard)
if err != nil {
return err
}
resp.Body.Close()
if resp.StatusCode != http.StatusNoContent {
return fmt.Errorf("expected status code %d", http.StatusNoContent)
}
return nil
}

55
server/playbooks/client/settings.go Обычный файл
Просмотреть файл

@@ -0,0 +1,55 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package client
import (
"context"
"net/http"
)
type GlobalSettings struct {
// EnableExperimentalFeatures is a read-only field set to true when experimental features
// are enabled. Changing this field requires access to the system console plugin
// configuration.
EnableExperimentalFeatures bool `json:"enable_experimental_features"`
}
// SettingsService handles communication with the settings related methods.
type SettingsService struct {
client *Client
}
// Get the configured settings.
func (s *SettingsService) Get(ctx context.Context) (*GlobalSettings, error) {
settingsURL := "settings"
req, err := s.client.newRequest(http.MethodGet, settingsURL, nil)
if err != nil {
return nil, err
}
settings := new(GlobalSettings)
resp, err := s.client.do(ctx, req, settings)
if err != nil {
return nil, err
}
resp.Body.Close()
return settings, nil
}
// Update the configured settings.
func (s *SettingsService) Update(ctx context.Context, settings GlobalSettings) error {
settingsURL := "settings"
req, err := s.client.newRequest(http.MethodPut, settingsURL, settings)
if err != nil {
return err
}
_, err = s.client.do(ctx, req, nil)
if err != nil {
return err
}
return nil
}

38
server/playbooks/client/stats.go Обычный файл
Просмотреть файл

@@ -0,0 +1,38 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package client
import (
"context"
"net/http"
)
// StatsService handles communication with the stats related methods.
type StatsService struct {
client *Client
}
// PlaybookSiteStats holds the data that we want to expose in system console
type PlaybookSiteStats struct {
TotalPlaybooks int `json:"total_playbooks"`
TotalPlaybookRuns int `json:"total_playbook_runs"`
}
// Get the stats that should be displayed in system console.
func (s *StatsService) GetSiteStats(ctx context.Context) (*PlaybookSiteStats, error) {
statsURL := "stats/site"
req, err := s.client.newRequest(http.MethodGet, statsURL, nil)
if err != nil {
return nil, err
}
stats := new(PlaybookSiteStats)
resp, err := s.client.do(ctx, req, stats)
if err != nil {
return nil, err
}
resp.Body.Close()
return stats, nil
}

49
server/playbooks/client/telemetry.go Обычный файл
Просмотреть файл

@@ -0,0 +1,49 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package client
import (
"context"
"fmt"
"io/ioutil"
"net/http"
)
type TelemetryService struct {
client *Client
}
func (s *TelemetryService) CreateEvent(ctx context.Context, name string, eventType string, properties map[string]interface{}) error {
payload := struct {
Type string
Name string
Properties map[string]interface{}
}{
Type: eventType,
Name: name,
Properties: properties,
}
req, err := s.client.newRequest(http.MethodPost, "telemetry", payload)
if err != nil {
return err
}
resp, err := s.client.do(ctx, req, nil)
if err != nil {
return err
}
resp.Body.Close()
if resp.StatusCode != http.StatusNoContent {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return fmt.Errorf("expected status code %d, got %d: %s", http.StatusNoContent, resp.StatusCode, body)
}
return nil
}

7
server/playbooks/client/unexport_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package client
var BuildAPIURL = buildAPIURL
var NewClient = newClient