pushmetrics-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pm/__init__.py +1 -0
- pm/__main__.py +4 -0
- pm/_alias.py +164 -0
- pm/auth/__init__.py +0 -0
- pm/auth/device_flow.py +187 -0
- pm/auth/exchange.py +79 -0
- pm/auth/jwt_cache.py +163 -0
- pm/auth/secret_files.py +58 -0
- pm/auth/session.py +440 -0
- pm/auth/store.py +160 -0
- pm/auth/token.py +289 -0
- pm/cli.py +282 -0
- pm/client/__init__.py +68 -0
- pm/client/access_tokens.py +54 -0
- pm/client/base.py +229 -0
- pm/client/blocks.py +461 -0
- pm/client/databases.py +147 -0
- pm/client/favorites.py +14 -0
- pm/client/folders.py +10 -0
- pm/client/integrations.py +14 -0
- pm/client/organizations.py +20 -0
- pm/client/recipients.py +92 -0
- pm/client/reports.py +148 -0
- pm/client/roles.py +100 -0
- pm/client/runs.py +134 -0
- pm/client/saved_blocks.py +13 -0
- pm/client/tableau.py +108 -0
- pm/client/users.py +71 -0
- pm/client/webhooks.py +43 -0
- pm/client/workspaces.py +34 -0
- pm/commands/__init__.py +0 -0
- pm/commands/_block_factory.py +762 -0
- pm/commands/_helpers.py +178 -0
- pm/commands/auth.py +222 -0
- pm/commands/block.py +26 -0
- pm/commands/chart.py +20 -0
- pm/commands/db.py +291 -0
- pm/commands/email.py +13 -0
- pm/commands/explore.py +20 -0
- pm/commands/favorites.py +77 -0
- pm/commands/folders.py +113 -0
- pm/commands/help.py +643 -0
- pm/commands/integrations.py +95 -0
- pm/commands/metrics_view.py +195 -0
- pm/commands/organizations.py +56 -0
- pm/commands/recipient.py +226 -0
- pm/commands/reports.py +364 -0
- pm/commands/roles.py +102 -0
- pm/commands/runs.py +214 -0
- pm/commands/saved_blocks.py +97 -0
- pm/commands/sftp.py +19 -0
- pm/commands/slack.py +13 -0
- pm/commands/slides.py +216 -0
- pm/commands/sql.py +17 -0
- pm/commands/tableau.py +148 -0
- pm/commands/token.py +107 -0
- pm/commands/users.py +262 -0
- pm/commands/webhooks.py +132 -0
- pm/commands/workspace.py +156 -0
- pm/config.py +192 -0
- pm/errors.py +228 -0
- pm/log.py +63 -0
- pm/models/__init__.py +31 -0
- pm/models/access_tokens.py +25 -0
- pm/models/blocks.py +21 -0
- pm/models/databases.py +15 -0
- pm/models/favorites.py +14 -0
- pm/models/folders.py +14 -0
- pm/models/integrations.py +20 -0
- pm/models/organizations.py +13 -0
- pm/models/recipients.py +41 -0
- pm/models/reports.py +16 -0
- pm/models/roles.py +22 -0
- pm/models/runs.py +56 -0
- pm/models/saved_blocks.py +15 -0
- pm/models/users.py +36 -0
- pm/models/webhooks.py +23 -0
- pm/models/workspaces.py +16 -0
- pm/output.py +92 -0
- pm/types.py +130 -0
- pm/validators.py +499 -0
- pushmetrics_cli-0.1.0.dist-info/METADATA +221 -0
- pushmetrics_cli-0.1.0.dist-info/RECORD +86 -0
- pushmetrics_cli-0.1.0.dist-info/WHEEL +5 -0
- pushmetrics_cli-0.1.0.dist-info/entry_points.txt +2 -0
- pushmetrics_cli-0.1.0.dist-info/top_level.txt +1 -0
pm/commands/users.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""User commands."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from pm._alias import AliasedGroup
|
|
9
|
+
from pm.client import PMClient
|
|
10
|
+
from pm.commands._helpers import json_option, peek_name, yes_option, limit_option, name_filter_option, select_rows
|
|
11
|
+
from pm.output import emit
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# pm-tool: REST-aligned verbs are canonical; legacy `add`/`remove`/`ls` keep
|
|
15
|
+
# working as compatibility aliases so existing scripts don't break.
|
|
16
|
+
@click.group(
|
|
17
|
+
name="users",
|
|
18
|
+
cls=AliasedGroup,
|
|
19
|
+
aliases={
|
|
20
|
+
# legacy → canonical: old form keeps working, canonical is primary
|
|
21
|
+
"ls": "list",
|
|
22
|
+
"add": "create",
|
|
23
|
+
"remove": "delete",
|
|
24
|
+
},
|
|
25
|
+
)
|
|
26
|
+
def users() -> None:
|
|
27
|
+
"""Users in the current workspace."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _role_name(role) -> str:
|
|
31
|
+
if role is None:
|
|
32
|
+
return ""
|
|
33
|
+
if hasattr(role, "model_dump"):
|
|
34
|
+
role = role.model_dump()
|
|
35
|
+
if isinstance(role, dict):
|
|
36
|
+
return role.get("name") or str(role.get("id", ""))
|
|
37
|
+
return str(role)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _format_roles(roles) -> str:
|
|
41
|
+
if not roles:
|
|
42
|
+
return ""
|
|
43
|
+
return ", ".join(_role_name(r) for r in roles)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@users.command(name="list")
|
|
47
|
+
@name_filter_option(matches="email, first or last name")
|
|
48
|
+
@limit_option
|
|
49
|
+
@json_option
|
|
50
|
+
@click.pass_context
|
|
51
|
+
def ls(ctx: click.Context, name_filter: Optional[str], limit: Optional[int]) -> None:
|
|
52
|
+
"""List users in the current workspace."""
|
|
53
|
+
profile = ctx.obj["profile"]
|
|
54
|
+
json_output = ctx.obj["json"]
|
|
55
|
+
|
|
56
|
+
client = PMClient.from_profile(profile)
|
|
57
|
+
items = select_rows(
|
|
58
|
+
client.users.list(),
|
|
59
|
+
name=name_filter,
|
|
60
|
+
fields=("email", "first_name", "last_name"),
|
|
61
|
+
limit=limit,
|
|
62
|
+
)
|
|
63
|
+
client.persist()
|
|
64
|
+
|
|
65
|
+
emit(
|
|
66
|
+
items,
|
|
67
|
+
json_output=json_output,
|
|
68
|
+
columns=[
|
|
69
|
+
("UID", "uid"),
|
|
70
|
+
("EMAIL", "email"),
|
|
71
|
+
("FIRST", "first_name"),
|
|
72
|
+
("LAST", "last_name"),
|
|
73
|
+
("MAIN_ROLE", lambda r: _role_name(r.get("main_role"))),
|
|
74
|
+
("ROLES", lambda r: _format_roles(r.get("roles"))),
|
|
75
|
+
("LAST_LOGIN", "last_login"),
|
|
76
|
+
],
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@users.command(name="create")
|
|
81
|
+
@click.argument("email")
|
|
82
|
+
@click.option("--first-name", default="", help="First name.")
|
|
83
|
+
@click.option("--last-name", default="", help="Last name.")
|
|
84
|
+
@click.option(
|
|
85
|
+
"--role-id",
|
|
86
|
+
"role_ids",
|
|
87
|
+
type=int,
|
|
88
|
+
multiple=True,
|
|
89
|
+
help=(
|
|
90
|
+
"Numeric role id to assign (from pm role list). Without it the "
|
|
91
|
+
"workspace's default roles apply. Repeatable."
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
@click.option(
|
|
95
|
+
"--skip-welcome-email",
|
|
96
|
+
is_flag=True,
|
|
97
|
+
help="Do not send the invitation email.",
|
|
98
|
+
)
|
|
99
|
+
@json_option
|
|
100
|
+
@click.pass_context
|
|
101
|
+
def add(
|
|
102
|
+
ctx: click.Context,
|
|
103
|
+
email: str,
|
|
104
|
+
first_name: str,
|
|
105
|
+
last_name: str,
|
|
106
|
+
role_ids: tuple,
|
|
107
|
+
skip_welcome_email: bool,
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Invite a user to the current workspace.
|
|
110
|
+
|
|
111
|
+
An existing user of the organization is added to the workspace; a
|
|
112
|
+
new address gets an account and a welcome email (unless
|
|
113
|
+
--skip-welcome-email).
|
|
114
|
+
"""
|
|
115
|
+
profile = ctx.obj["profile"]
|
|
116
|
+
json_output = ctx.obj["json"]
|
|
117
|
+
|
|
118
|
+
payload = {
|
|
119
|
+
"email": email,
|
|
120
|
+
"first_name": first_name,
|
|
121
|
+
"last_name": last_name,
|
|
122
|
+
"roles": list(role_ids),
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
client = PMClient.from_profile(profile)
|
|
126
|
+
user, action = client.users.create(payload, skip_welcome_email=skip_welcome_email)
|
|
127
|
+
client.persist()
|
|
128
|
+
|
|
129
|
+
if json_output:
|
|
130
|
+
out = user.model_dump()
|
|
131
|
+
if action is not None:
|
|
132
|
+
out["action"] = action
|
|
133
|
+
emit(out, json_output=True)
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
# Map the backend's action to a clear one-line outcome so callers
|
|
137
|
+
# don't have to compare workspace member lists themselves.
|
|
138
|
+
label = {
|
|
139
|
+
"created": "created",
|
|
140
|
+
"added_to_workspace": "added existing user to workspace",
|
|
141
|
+
"no_op": "already in this workspace (no change)",
|
|
142
|
+
}.get(action or "", "ok")
|
|
143
|
+
click.echo(f"{label}: {user.uid} {user.email} roles={_format_roles(user.roles)}")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@users.command(name="delete")
|
|
147
|
+
@click.argument("user_uid")
|
|
148
|
+
@click.option(
|
|
149
|
+
"--workspace",
|
|
150
|
+
"workspace_uid",
|
|
151
|
+
default=None,
|
|
152
|
+
help="Workspace uid to remove the user from (default: the active workspace).",
|
|
153
|
+
)
|
|
154
|
+
@yes_option
|
|
155
|
+
@json_option
|
|
156
|
+
@click.pass_context
|
|
157
|
+
def remove(
|
|
158
|
+
ctx: click.Context,
|
|
159
|
+
user_uid: str,
|
|
160
|
+
workspace_uid: Optional[str],
|
|
161
|
+
yes: bool,
|
|
162
|
+
) -> None:
|
|
163
|
+
"""Remove a user from a workspace.
|
|
164
|
+
|
|
165
|
+
The user loses their roles in that workspace. If it was their last
|
|
166
|
+
workspace, the account is deactivated, removed from the organization
|
|
167
|
+
and its email anonymised. This is the way to offboard a user.
|
|
168
|
+
"""
|
|
169
|
+
profile = ctx.obj["profile"]
|
|
170
|
+
json_output = ctx.obj["json"]
|
|
171
|
+
|
|
172
|
+
if not yes:
|
|
173
|
+
label = peek_name(
|
|
174
|
+
lambda: PMClient.from_profile(profile).users.get(user_uid), "email"
|
|
175
|
+
) or user_uid
|
|
176
|
+
target = f"workspace {workspace_uid}" if workspace_uid else "the active workspace"
|
|
177
|
+
click.confirm(
|
|
178
|
+
f"Remove user {label!r} from {target}? "
|
|
179
|
+
"If this is their last workspace they will be deactivated.",
|
|
180
|
+
abort=True,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
client = PMClient.from_profile(profile)
|
|
184
|
+
client.users.remove_from_workspace(user_uid, workspace_uid)
|
|
185
|
+
client.persist()
|
|
186
|
+
|
|
187
|
+
if json_output:
|
|
188
|
+
emit(
|
|
189
|
+
{
|
|
190
|
+
"removed_user": user_uid,
|
|
191
|
+
"workspace": workspace_uid or "active",
|
|
192
|
+
},
|
|
193
|
+
json_output=True,
|
|
194
|
+
)
|
|
195
|
+
return
|
|
196
|
+
click.echo(f"Removed user {user_uid} from {workspace_uid or 'the active workspace'}.", err=True)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@users.command(name="set-role")
|
|
200
|
+
@click.argument("user_uid")
|
|
201
|
+
@click.argument("main_role", type=click.Choice(["admin", "user", "guest"], case_sensitive=False))
|
|
202
|
+
@json_option
|
|
203
|
+
@click.pass_context
|
|
204
|
+
def set_role(ctx: click.Context, user_uid: str, main_role: str) -> None:
|
|
205
|
+
"""Set a user's main role in the current workspace.
|
|
206
|
+
|
|
207
|
+
Replaces the current main role; group memberships are unchanged.
|
|
208
|
+
"""
|
|
209
|
+
profile = ctx.obj["profile"]
|
|
210
|
+
json_output = ctx.obj["json"]
|
|
211
|
+
|
|
212
|
+
client = PMClient.from_profile(profile)
|
|
213
|
+
role_id = client.roles.find_main_role_id(main_role)
|
|
214
|
+
user = client.users.update(user_uid, {"main_role": role_id})
|
|
215
|
+
client.persist()
|
|
216
|
+
|
|
217
|
+
if json_output:
|
|
218
|
+
emit(user, json_output=True)
|
|
219
|
+
return
|
|
220
|
+
emit(
|
|
221
|
+
{
|
|
222
|
+
"uid": user.uid,
|
|
223
|
+
"email": user.email,
|
|
224
|
+
"main_role": _role_name(user.main_role),
|
|
225
|
+
"roles": _format_roles(user.roles),
|
|
226
|
+
},
|
|
227
|
+
json_output=False,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@users.command(name="get")
|
|
232
|
+
@click.argument("user_id", metavar="USER_REF")
|
|
233
|
+
@json_option
|
|
234
|
+
@click.pass_context
|
|
235
|
+
def get(ctx: click.Context, user_id: str) -> None:
|
|
236
|
+
"""Show one user (use --json for the full record).
|
|
237
|
+
|
|
238
|
+
USER_REF is the user's uid or numeric id.
|
|
239
|
+
"""
|
|
240
|
+
profile = ctx.obj["profile"]
|
|
241
|
+
json_output = ctx.obj["json"]
|
|
242
|
+
|
|
243
|
+
client = PMClient.from_profile(profile)
|
|
244
|
+
user = client.users.get(user_id)
|
|
245
|
+
client.persist()
|
|
246
|
+
|
|
247
|
+
if json_output:
|
|
248
|
+
emit(user, json_output=True)
|
|
249
|
+
else:
|
|
250
|
+
emit(
|
|
251
|
+
{
|
|
252
|
+
"id": user.id,
|
|
253
|
+
"uid": user.uid,
|
|
254
|
+
"email": user.email,
|
|
255
|
+
"first_name": user.first_name,
|
|
256
|
+
"last_name": user.last_name,
|
|
257
|
+
"main_role": _role_name(user.main_role),
|
|
258
|
+
"roles": _format_roles(user.roles),
|
|
259
|
+
"last_login": user.last_login,
|
|
260
|
+
},
|
|
261
|
+
json_output=False,
|
|
262
|
+
)
|
pm/commands/webhooks.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Webhook commands (full CRUD).
|
|
2
|
+
|
|
3
|
+
The backend JSON-stringifies `details`/`recipients`/`slack_recipients` on input;
|
|
4
|
+
the client handles that. Pass these via `--data '<json>'` for structured input,
|
|
5
|
+
or as plain strings via the explicit flags.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
|
|
13
|
+
from pm._alias import AliasedGroup
|
|
14
|
+
from pm.client import PMClient
|
|
15
|
+
from pm.commands._helpers import (
|
|
16
|
+
confirm_delete,
|
|
17
|
+
json_option,
|
|
18
|
+
merge_fields,
|
|
19
|
+
parse_data,
|
|
20
|
+
peek_name,
|
|
21
|
+
report_deleted,
|
|
22
|
+
yes_option,
|
|
23
|
+
limit_option,
|
|
24
|
+
select_rows,
|
|
25
|
+
)
|
|
26
|
+
from pm.output import emit
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@click.group(name="webhooks", cls=AliasedGroup, aliases={"ls": "list"})
|
|
30
|
+
def webhooks() -> None:
|
|
31
|
+
"""Webhooks that trigger report runs."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@webhooks.command(name="list")
|
|
35
|
+
@limit_option
|
|
36
|
+
@json_option
|
|
37
|
+
@click.pass_context
|
|
38
|
+
def ls(ctx: click.Context, limit: Optional[int]) -> None:
|
|
39
|
+
"""List webhooks (ids only).
|
|
40
|
+
|
|
41
|
+
The list shows each webhook's numeric id; pm webhook get <id> shows
|
|
42
|
+
the rest.
|
|
43
|
+
"""
|
|
44
|
+
client = PMClient.from_profile(ctx.obj["profile"])
|
|
45
|
+
items = select_rows(client.webhooks.list(), limit=limit)
|
|
46
|
+
client.persist()
|
|
47
|
+
emit(
|
|
48
|
+
items,
|
|
49
|
+
json_output=ctx.obj["json"],
|
|
50
|
+
# Only `id` is meaningful — other columns are null until backend adds list_columns.
|
|
51
|
+
columns=[("ID", "id")],
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@webhooks.command(name="get")
|
|
56
|
+
@click.argument("uid")
|
|
57
|
+
@json_option
|
|
58
|
+
@click.pass_context
|
|
59
|
+
def get(ctx: click.Context, uid: str) -> None:
|
|
60
|
+
"""Show one webhook."""
|
|
61
|
+
client = PMClient.from_profile(ctx.obj["profile"])
|
|
62
|
+
wh = client.webhooks.get(uid)
|
|
63
|
+
client.persist()
|
|
64
|
+
emit(wh, json_output=ctx.obj["json"])
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@webhooks.command(name="create")
|
|
68
|
+
@click.option("--report-id", type=int, required=True,
|
|
69
|
+
help="Numeric id of the report to trigger (from pm report get --json).")
|
|
70
|
+
@click.option("--token", default=None,
|
|
71
|
+
help="Secret the caller must present. Generated if omitted.")
|
|
72
|
+
@click.option("--active/--inactive", default=True, show_default=True,
|
|
73
|
+
help="Whether the webhook accepts calls.")
|
|
74
|
+
@click.option("--data", default=None, help="Extra fields (details, recipients, slack_recipients) as a JSON object.")
|
|
75
|
+
@json_option
|
|
76
|
+
@click.pass_context
|
|
77
|
+
def create(
|
|
78
|
+
ctx: click.Context,
|
|
79
|
+
report_id: int,
|
|
80
|
+
token: Optional[str],
|
|
81
|
+
active: bool,
|
|
82
|
+
data: Optional[str],
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Create a webhook for a report."""
|
|
85
|
+
payload = merge_fields(
|
|
86
|
+
parse_data(data),
|
|
87
|
+
{"report_id": report_id, "token": token, "active": active},
|
|
88
|
+
)
|
|
89
|
+
client = PMClient.from_profile(ctx.obj["profile"])
|
|
90
|
+
wh = client.webhooks.create(payload)
|
|
91
|
+
client.persist()
|
|
92
|
+
emit(wh, json_output=ctx.obj["json"])
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@webhooks.command(name="update")
|
|
96
|
+
@click.argument("uid")
|
|
97
|
+
@click.option("--token", default=None, help="Replace the secret the caller must present.")
|
|
98
|
+
@click.option("--active/--inactive", default=None, help="Enable or disable the webhook.")
|
|
99
|
+
@click.option("--data", default=None, help="Extra fields as a JSON object.")
|
|
100
|
+
@json_option
|
|
101
|
+
@click.pass_context
|
|
102
|
+
def update(
|
|
103
|
+
ctx: click.Context,
|
|
104
|
+
uid: str,
|
|
105
|
+
token: Optional[str],
|
|
106
|
+
active: Optional[bool],
|
|
107
|
+
data: Optional[str],
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Update a webhook."""
|
|
110
|
+
payload = merge_fields(parse_data(data), {"token": token, "active": active})
|
|
111
|
+
if not payload:
|
|
112
|
+
raise click.UsageError("Nothing to update; pass --token, --active/--inactive, or --data.")
|
|
113
|
+
client = PMClient.from_profile(ctx.obj["profile"])
|
|
114
|
+
wh = client.webhooks.update(uid, payload)
|
|
115
|
+
client.persist()
|
|
116
|
+
emit(wh, json_output=ctx.obj["json"])
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@webhooks.command(name="delete")
|
|
120
|
+
@click.argument("uid")
|
|
121
|
+
@yes_option
|
|
122
|
+
@json_option
|
|
123
|
+
@click.pass_context
|
|
124
|
+
def delete(ctx: click.Context, uid: str, yes: bool) -> None:
|
|
125
|
+
"""Delete a webhook."""
|
|
126
|
+
if not yes:
|
|
127
|
+
name = peek_name(lambda: PMClient.from_profile(ctx.obj["profile"]).webhooks.get(uid), "name")
|
|
128
|
+
confirm_delete("webhook", uid, name)
|
|
129
|
+
client = PMClient.from_profile(ctx.obj["profile"])
|
|
130
|
+
client.webhooks.delete(uid)
|
|
131
|
+
client.persist()
|
|
132
|
+
report_deleted(ctx, "webhook", uid)
|
pm/commands/workspace.py
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Workspace commands."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from pm._alias import AliasedGroup
|
|
9
|
+
from pm.client import PMClient
|
|
10
|
+
from pm.commands._helpers import (
|
|
11
|
+
confirm_delete,
|
|
12
|
+
json_option,
|
|
13
|
+
peek_name,
|
|
14
|
+
report_deleted,
|
|
15
|
+
yes_option,
|
|
16
|
+
limit_option,
|
|
17
|
+
name_filter_option,
|
|
18
|
+
select_rows,
|
|
19
|
+
)
|
|
20
|
+
from pm.output import emit
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@click.group(name="workspace", cls=AliasedGroup, aliases={"ls": "list"})
|
|
24
|
+
def workspace() -> None:
|
|
25
|
+
"""Workspaces you belong to."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@workspace.command(name="list")
|
|
29
|
+
@name_filter_option
|
|
30
|
+
@limit_option
|
|
31
|
+
@json_option
|
|
32
|
+
@click.pass_context
|
|
33
|
+
def ls(ctx: click.Context, name_filter: Optional[str], limit: Optional[int]) -> None:
|
|
34
|
+
"""List workspaces you have access to."""
|
|
35
|
+
profile = ctx.obj["profile"]
|
|
36
|
+
json_output = ctx.obj["json"]
|
|
37
|
+
|
|
38
|
+
client = PMClient.from_profile(profile)
|
|
39
|
+
workspaces_list = select_rows(client.workspaces.list(), name=name_filter, limit=limit)
|
|
40
|
+
client.persist()
|
|
41
|
+
|
|
42
|
+
emit(
|
|
43
|
+
workspaces_list,
|
|
44
|
+
json_output=json_output,
|
|
45
|
+
columns=[
|
|
46
|
+
("ACTIVE", lambda r: "*" if r.get("is_active") else ""),
|
|
47
|
+
("UID", "uid"),
|
|
48
|
+
("NAME", "name"),
|
|
49
|
+
("ORGANIZATION", lambda r: (r.get("organization") or {}).get("name") if isinstance(r.get("organization"), dict) else None),
|
|
50
|
+
],
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@workspace.command(name="current")
|
|
55
|
+
@json_option
|
|
56
|
+
@click.pass_context
|
|
57
|
+
def current(ctx: click.Context) -> None:
|
|
58
|
+
"""Show the active workspace."""
|
|
59
|
+
profile = ctx.obj["profile"]
|
|
60
|
+
json_output = ctx.obj["json"]
|
|
61
|
+
|
|
62
|
+
client = PMClient.from_profile(profile)
|
|
63
|
+
ws = client.workspaces.current()
|
|
64
|
+
client.persist()
|
|
65
|
+
|
|
66
|
+
emit(
|
|
67
|
+
{
|
|
68
|
+
"uid": ws.uid,
|
|
69
|
+
"name": ws.name,
|
|
70
|
+
"id": ws.id,
|
|
71
|
+
},
|
|
72
|
+
json_output=json_output,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@workspace.command(name="switch")
|
|
77
|
+
@click.argument("uid")
|
|
78
|
+
@json_option
|
|
79
|
+
@click.pass_context
|
|
80
|
+
def switch(ctx: click.Context, uid: str) -> None:
|
|
81
|
+
"""Switch the active workspace (browser login only).
|
|
82
|
+
|
|
83
|
+
Not available with a personal access token: a token is bound to the
|
|
84
|
+
workspace it was created in. To work in another workspace, create a
|
|
85
|
+
token there and run pm login --token with it.
|
|
86
|
+
"""
|
|
87
|
+
profile = ctx.obj["profile"]
|
|
88
|
+
json_output = ctx.obj["json"]
|
|
89
|
+
|
|
90
|
+
client = PMClient.from_profile(profile)
|
|
91
|
+
ws = client.workspaces.switch(uid)
|
|
92
|
+
|
|
93
|
+
emit(
|
|
94
|
+
{
|
|
95
|
+
"switched_to": ws.name,
|
|
96
|
+
"uid": ws.uid,
|
|
97
|
+
},
|
|
98
|
+
json_output=json_output,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@workspace.command(name="delete")
|
|
103
|
+
@click.argument("uid")
|
|
104
|
+
@yes_option
|
|
105
|
+
@json_option
|
|
106
|
+
@click.pass_context
|
|
107
|
+
def delete(ctx: click.Context, uid: str, yes: bool) -> None:
|
|
108
|
+
"""Delete a workspace and everything in it.
|
|
109
|
+
|
|
110
|
+
All reports, agents and roles in the workspace are removed. This
|
|
111
|
+
cannot be undone. You cannot delete the workspace you are working in,
|
|
112
|
+
or your only workspace.
|
|
113
|
+
"""
|
|
114
|
+
profile = ctx.obj["profile"]
|
|
115
|
+
|
|
116
|
+
if not yes:
|
|
117
|
+
name = peek_name(lambda: PMClient.from_profile(profile).workspaces.get(uid), "name")
|
|
118
|
+
confirm_delete(
|
|
119
|
+
"workspace", uid, name,
|
|
120
|
+
note="All reports, agents and roles in it will be gone.",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
client = PMClient.from_profile(profile)
|
|
124
|
+
client.workspaces.delete(uid)
|
|
125
|
+
client.persist()
|
|
126
|
+
report_deleted(ctx, "workspace", uid)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@workspace.command(name="create")
|
|
130
|
+
@click.argument("name")
|
|
131
|
+
@json_option
|
|
132
|
+
@click.pass_context
|
|
133
|
+
def create(ctx: click.Context, name: str) -> None:
|
|
134
|
+
"""Create a workspace in your organization.
|
|
135
|
+
|
|
136
|
+
You become its first member; it starts with the default roles and a
|
|
137
|
+
default agent.
|
|
138
|
+
"""
|
|
139
|
+
profile = ctx.obj["profile"]
|
|
140
|
+
json_output = ctx.obj["json"]
|
|
141
|
+
|
|
142
|
+
client = PMClient.from_profile(profile)
|
|
143
|
+
ws = client.workspaces.create({"name": name})
|
|
144
|
+
client.persist()
|
|
145
|
+
|
|
146
|
+
if json_output:
|
|
147
|
+
emit(ws, json_output=True)
|
|
148
|
+
return
|
|
149
|
+
emit(
|
|
150
|
+
{
|
|
151
|
+
"uid": ws.uid,
|
|
152
|
+
"name": ws.name,
|
|
153
|
+
"id": ws.id,
|
|
154
|
+
},
|
|
155
|
+
json_output=False,
|
|
156
|
+
)
|