Skip to content

Commit

Permalink
Add is_secret to Field (#1289)
Browse files Browse the repository at this point in the history
Adding the ability to mark a field as secret. We could use this to hide strings in dagit by default just like a password text box in a web browser.

#1290 tracks the client side impl.
  • Loading branch information
schrockn committed Apr 23, 2019
1 parent 854c92e commit f30323f
Show file tree
Hide file tree
Showing 9 changed files with 283 additions and 193 deletions.
1 change: 1 addition & 0 deletions js_modules/dagit/src/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type ConfigTypeField {
configType: ConfigType!
defaultValue: String
isOptional: Boolean!
isSecret: Boolean!
}

type ConfigTypeNotFoundError implements Error {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from dagster import check
from dagster.core.types.config import ConfigType
from dagster.core.types.field_utils import FieldImpl

from dagster_graphql import dauphin

Expand Down Expand Up @@ -186,13 +187,18 @@ class Meta:
config_type = dauphin.NonNull('ConfigType')
default_value = dauphin.String()
is_optional = dauphin.NonNull(dauphin.Boolean)
is_secret = dauphin.NonNull(dauphin.Boolean)

def __init__(self, name, field):
check.str_param(name, 'name')
check.inst_param(field, 'field', FieldImpl)

super(DauphinConfigTypeField, self).__init__(
name=name,
description=field.description,
default_value=field.default_value_as_str if field.default_provided else None,
is_optional=field.is_optional,
is_secret=field.is_secret,
)
self._field = field

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,23 @@ def define_repository():
'scalar_output_pipeline': define_scalar_output_pipeline,
'pipeline_with_enum_config': define_pipeline_with_enum_config,
'naughty_programmer_pipeline': define_naughty_programmer_pipeline,
'secret_pipeline': define_pipeline_with_secret,
},
)


def define_pipeline_with_secret():
@solid(
config_field=Field(
Dict({'password': Field(String, is_secret=True), 'notpassword': Field(String)})
)
)
def solid_with_secret(_context):
pass

return PipelineDefinition(name='secret_pipeline', solids=[solid_with_secret])


def define_context_config_pipeline():
return PipelineDefinition(
name='context_config_pipeline',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from dagster import check
from dagster.utils import script_relative_path
from dagster.core.types.config import ALL_CONFIG_BUILTINS

from .setup import execute_dagster_graphql, define_context, pandas_hello_world_solids_config

CONFIG_VALIDATION_QUERY = '''
Expand Down Expand Up @@ -470,3 +473,227 @@ def test_config_list_item_invalid():
last_entry = entries[3]
assert last_entry['__typename'] == 'EvaluationStackListItemEntry'
assert last_entry['listIndex'] == 1


def pipeline_named(result, name):
for pipeline_data in result.data['pipelines']['nodes']:
if pipeline_data['name'] == name:
return pipeline_data
check.failed('Did not find')


def has_config_type_with_key_prefix(pipeline_data, prefix):
for config_type_data in pipeline_data['configTypes']:
if config_type_data['key'].startswith(prefix):
return True

return False


def has_config_type(pipeline_data, name):
for config_type_data in pipeline_data['configTypes']:
if config_type_data['name'] == name:
return True

return False


def test_smoke_test_config_type_system():
result = execute_dagster_graphql(define_context(), ALL_CONFIG_TYPES_QUERY)

assert not result.errors
assert result.data

pipeline_data = pipeline_named(result, 'more_complicated_nested_config')

assert pipeline_data

assert has_config_type_with_key_prefix(pipeline_data, 'Dict.')
assert not has_config_type_with_key_prefix(pipeline_data, 'List.')
assert not has_config_type_with_key_prefix(pipeline_data, 'Nullable.')

for builtin_config_type in ALL_CONFIG_BUILTINS:
assert has_config_type(pipeline_data, builtin_config_type.name)


ALL_CONFIG_TYPES_QUERY = '''
fragment configTypeFragment on ConfigType {
__typename
key
name
description
isNullable
isList
isSelector
isBuiltin
isSystemGenerated
innerTypes {
key
name
description
... on CompositeConfigType {
fields {
name
isOptional
isSecret
description
}
}
... on WrappingConfigType {
ofType { key }
}
}
... on EnumConfigType {
values {
value
description
}
}
... on CompositeConfigType {
fields {
name
isOptional
isSecret
description
}
}
... on WrappingConfigType {
ofType { key }
}
}
{
pipelines {
nodes {
name
configTypes {
...configTypeFragment
}
}
}
}
'''

CONFIG_TYPE_QUERY = '''
query ConfigTypeQuery($pipelineName: String! $configTypeName: String!)
{
configTypeOrError(
pipelineName: $pipelineName
configTypeName: $configTypeName
) {
__typename
... on RegularConfigType {
name
}
... on CompositeConfigType {
name
innerTypes { key name }
fields { name configType { key name } }
}
... on EnumConfigType {
name
}
... on PipelineNotFoundError {
pipelineName
}
... on ConfigTypeNotFoundError {
pipeline { name }
configTypeName
}
}
}
'''


def test_config_type_or_error_query_success():
result = execute_dagster_graphql(
define_context(),
CONFIG_TYPE_QUERY,
{'pipelineName': 'pandas_hello_world', 'configTypeName': 'PandasHelloWorld.Environment'},
)

assert not result.errors
assert result.data
assert result.data['configTypeOrError']['__typename'] == 'CompositeConfigType'
assert result.data['configTypeOrError']['name'] == 'PandasHelloWorld.Environment'


def test_config_type_or_error_pipeline_not_found():
result = execute_dagster_graphql(
define_context(),
CONFIG_TYPE_QUERY,
{'pipelineName': 'nope', 'configTypeName': 'PandasHelloWorld.Environment'},
)

assert not result.errors
assert result.data
assert result.data['configTypeOrError']['__typename'] == 'PipelineNotFoundError'
assert result.data['configTypeOrError']['pipelineName'] == 'nope'


def test_config_type_or_error_type_not_found():
result = execute_dagster_graphql(
define_context(),
CONFIG_TYPE_QUERY,
{'pipelineName': 'pandas_hello_world', 'configTypeName': 'nope'},
)

assert not result.errors
assert result.data
assert result.data['configTypeOrError']['__typename'] == 'ConfigTypeNotFoundError'
assert result.data['configTypeOrError']['pipeline']['name'] == 'pandas_hello_world'
assert result.data['configTypeOrError']['configTypeName'] == 'nope'


def test_config_type_or_error_nested_complicated():
result = execute_dagster_graphql(
define_context(),
CONFIG_TYPE_QUERY,
{
'pipelineName': 'more_complicated_nested_config',
'configTypeName': (
'MoreComplicatedNestedConfig.SolidConfig.ASolidWithMultilayeredConfig'
),
},
)

assert not result.errors
assert result.data
assert result.data['configTypeOrError']['__typename'] == 'CompositeConfigType'
assert (
result.data['configTypeOrError']['name']
== 'MoreComplicatedNestedConfig.SolidConfig.ASolidWithMultilayeredConfig'
)
assert len(result.data['configTypeOrError']['innerTypes']) == 6


def test_graphql_secret_field():
result = execute_dagster_graphql(
define_context(), ALL_CONFIG_TYPES_QUERY, {'pipelineName': 'secret_pipeline'}
)

password_type_count = 0

assert not result.errors
assert result.data
for pipeline_data in result.data['pipelines']['nodes']:
for config_type_data in pipeline_data['configTypes']:
if 'password' in get_field_names(config_type_data):
password_field = get_field_data(config_type_data, 'password')
assert password_field['isSecret']
notpassword_field = get_field_data(config_type_data, 'notpassword')
assert not notpassword_field['isSecret']

password_type_count += 1

assert password_type_count == 1


def get_field_data(config_type_data, name):
for field_data in config_type_data['fields']:
if field_data['name'] == name:
return field_data


def get_field_names(config_type_data):
return {field_data['name'] for field_data in config_type_data.get('fields', [])}

0 comments on commit f30323f

Please sign in to comment.